mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-22 08:01:00 +00:00
Deploy and presence stub
This commit is contained in:
parent
1a68db73f3
commit
364b0659e1
24 changed files with 332 additions and 55 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
priv/uploads
|
||||||
|
deps/
|
98
Dockerfile
Normal file
98
Dockerfile
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian instead of
|
||||||
|
# Alpine to avoid DNS resolution issues in production.
|
||||||
|
#
|
||||||
|
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
|
||||||
|
# https://hub.docker.com/_/ubuntu?tab=tags
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This file is based on these images:
|
||||||
|
#
|
||||||
|
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
|
||||||
|
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20210902-slim - for the release image
|
||||||
|
# - https://pkgs.org/ - resource for finding needed packages
|
||||||
|
# - Ex: hexpm/elixir:1.12.3-erlang-24.1.4-debian-bullseye-20210902-slim
|
||||||
|
#
|
||||||
|
ARG BUILDER_IMAGE="hexpm/elixir:1.12.3-erlang-24.1.4-debian-bullseye-20210902-slim"
|
||||||
|
ARG RUNNER_IMAGE="debian:bullseye-20210902-slim"
|
||||||
|
|
||||||
|
FROM ${BUILDER_IMAGE} as builder
|
||||||
|
|
||||||
|
# install build dependencies
|
||||||
|
RUN apt-get update -y && apt-get install -y build-essential git \
|
||||||
|
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
|
||||||
|
|
||||||
|
# prepare build dir
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# install hex + rebar
|
||||||
|
RUN mix local.hex --force && \
|
||||||
|
mix local.rebar --force
|
||||||
|
|
||||||
|
# set build ENV
|
||||||
|
ENV MIX_ENV="prod"
|
||||||
|
|
||||||
|
# install mix dependencies
|
||||||
|
COPY mix.exs mix.lock ./
|
||||||
|
RUN mix deps.get --only $MIX_ENV
|
||||||
|
RUN mkdir config
|
||||||
|
|
||||||
|
# copy compile-time config files before we compile dependencies
|
||||||
|
# to ensure any relevant config change will trigger the dependencies
|
||||||
|
# to be re-compiled.
|
||||||
|
COPY config/config.exs config/${MIX_ENV}.exs config/
|
||||||
|
RUN mix deps.compile
|
||||||
|
|
||||||
|
COPY priv priv
|
||||||
|
|
||||||
|
# note: if your project uses a tool like https://purgecss.com/,
|
||||||
|
# which customizes asset compilation based on what it finds in
|
||||||
|
# your Elixir templates, you will need to move the asset compilation
|
||||||
|
# step down so that `lib` is available.
|
||||||
|
COPY assets assets
|
||||||
|
|
||||||
|
# For Phoenix 1.6 and later, compile assets using esbuild
|
||||||
|
RUN mix assets.deploy
|
||||||
|
|
||||||
|
# For Phoenix versions earlier than 1.6, compile assets npm
|
||||||
|
# RUN cd assets && yarn install && yarn run webpack --mode production
|
||||||
|
# RUN mix phx.digest
|
||||||
|
|
||||||
|
# Compile the release
|
||||||
|
COPY lib lib
|
||||||
|
|
||||||
|
RUN mix compile
|
||||||
|
|
||||||
|
# Changes to config/runtime.exs don't require recompiling the code
|
||||||
|
COPY config/runtime.exs config/
|
||||||
|
|
||||||
|
COPY rel rel
|
||||||
|
RUN mix release
|
||||||
|
|
||||||
|
# start a new build stage so that the final image will only contain
|
||||||
|
# the compiled release and other runtime necessities
|
||||||
|
FROM ${RUNNER_IMAGE}
|
||||||
|
|
||||||
|
RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales \
|
||||||
|
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
|
||||||
|
|
||||||
|
# Set the locale
|
||||||
|
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
|
||||||
|
WORKDIR "/app"
|
||||||
|
RUN chown nobody /app
|
||||||
|
|
||||||
|
# Only copy the final release from the build stage
|
||||||
|
COPY --from=builder --chown=nobody:root /app/_build/prod/rel ./
|
||||||
|
|
||||||
|
USER nobody
|
||||||
|
|
||||||
|
# Create a symlink to the application directory by extracting the directory name. This is required
|
||||||
|
# since the release directory will be named after the application, and we don't know that name.
|
||||||
|
RUN set -eux; \
|
||||||
|
ln -nfs /app/$(basename *)/bin/$(basename *) /app/entry
|
||||||
|
|
||||||
|
CMD /app/entry start
|
|
@ -5,7 +5,9 @@ Play music together with Phoenix LiveView!
|
||||||
Visit [todo]() to try it out, or run locally:
|
Visit [todo]() to try it out, or run locally:
|
||||||
|
|
||||||
* Create a [Github OAuth app](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app)
|
* Create a [Github OAuth app](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app)
|
||||||
* Export your GitHub client ID and secret:
|
- Set the app homepage to `http://localhost:4000` and `Authorization callback URL` to `http://localhost:4000/oauth/callbacks/github`
|
||||||
|
- After completing the form, click "Generate a new client secret" to obtain your API secret
|
||||||
|
* Export your GitHub Client ID and secret:
|
||||||
|
|
||||||
export LIVE_BEATS_GITHUB_CLIENT_ID="..."
|
export LIVE_BEATS_GITHUB_CLIENT_ID="..."
|
||||||
export LIVE_BEATS_GITHUB_CLIENT_SECRET="..."
|
export LIVE_BEATS_GITHUB_CLIENT_SECRET="..."
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import "phoenix_html"
|
import "phoenix_html"
|
||||||
import {Socket} from "phoenix"
|
import {Socket} from "phoenix"
|
||||||
import {LiveSocket} from "./phoenix_live_view"
|
// import {LiveSocket} from "./phoenix_live_view"
|
||||||
// import {LiveSocket} from "phoenix_live_view"
|
import {LiveSocket} from "phoenix_live_view"
|
||||||
import topbar from "../vendor/topbar"
|
import topbar from "../vendor/topbar"
|
||||||
|
|
||||||
let nowSeconds = () => Math.round(Date.now() / 1000)
|
let nowSeconds = () => Math.round(Date.now() / 1000)
|
||||||
|
@ -33,10 +33,12 @@ Hooks.AudioPlayer = {
|
||||||
let enableAudio = () => {
|
let enableAudio = () => {
|
||||||
if(this.player.src){
|
if(this.player.src){
|
||||||
document.removeEventListener("click", enableAudio)
|
document.removeEventListener("click", enableAudio)
|
||||||
|
if(this.player.readyState === 0){
|
||||||
this.player.play().catch(error => null)
|
this.player.play().catch(error => null)
|
||||||
this.player.pause()
|
this.player.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
document.addEventListener("click", enableAudio)
|
document.addEventListener("click", enableAudio)
|
||||||
this.el.addEventListener("js:listen_now", () => this.play({sync: true}))
|
this.el.addEventListener("js:listen_now", () => this.play({sync: true}))
|
||||||
this.el.addEventListener("js:play_pause", () => {
|
this.el.addEventListener("js:play_pause", () => {
|
||||||
|
|
|
@ -14,9 +14,11 @@ if config_env() == :prod do
|
||||||
For example: ecto://USER:PASS@HOST/DATABASE
|
For example: ecto://USER:PASS@HOST/DATABASE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ipv6? = !!System.get_env("IPV6")
|
||||||
|
|
||||||
config :live_beats, LiveBeats.Repo,
|
config :live_beats, LiveBeats.Repo,
|
||||||
# ssl: true,
|
# ssl: true,
|
||||||
# socket_options: [:inet6],
|
socket_options: if(ipv6?, do: [:inet6], else: []),
|
||||||
url: database_url,
|
url: database_url,
|
||||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||||
|
|
||||||
|
@ -27,7 +29,11 @@ if config_env() == :prod do
|
||||||
You can generate one by calling: mix phx.gen.secret
|
You can generate one by calling: mix phx.gen.secret
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
app_name = System.fetch_env!("FLY_APP_NAME")
|
||||||
|
host = System.get_env("URL_HOST") || "example.com"
|
||||||
|
|
||||||
config :live_beats, LiveBeatsWeb.Endpoint,
|
config :live_beats, LiveBeatsWeb.Endpoint,
|
||||||
|
url: [host: host, port: 80],
|
||||||
http: [
|
http: [
|
||||||
# Enable IPv6 and bind on all interfaces.
|
# Enable IPv6 and bind on all interfaces.
|
||||||
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
# Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
|
||||||
|
@ -36,33 +42,18 @@ if config_env() == :prod do
|
||||||
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
||||||
port: String.to_integer(System.get_env("PORT") || "4000")
|
port: String.to_integer(System.get_env("PORT") || "4000")
|
||||||
],
|
],
|
||||||
secret_key_base: secret_key_base
|
check_origin: ["//#{host}"],
|
||||||
|
secret_key_base: secret_key_base,
|
||||||
|
server: true
|
||||||
|
|
||||||
# ## Using releases
|
config :live_beats, :file_host, %{
|
||||||
#
|
scheme: "http",
|
||||||
# If you are doing OTP releases, you need to instruct Phoenix
|
host: host,
|
||||||
# to start each relevant endpoint:
|
port: 80
|
||||||
#
|
}
|
||||||
# config :live_beats, LiveBeatsWeb.Endpoint, server: true
|
|
||||||
#
|
|
||||||
# Then you can assemble a release by calling `mix release`.
|
|
||||||
# See `mix help release` for more information.
|
|
||||||
|
|
||||||
# ## Configuring the mailer
|
config :live_beats, :github, %{
|
||||||
#
|
client_id: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_ID"),
|
||||||
# In production you need to configure the mailer to use a different adapter.
|
client_secret: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_SECRET"),
|
||||||
# Also, you may need to configure the Swoosh API client of your choice if you
|
}
|
||||||
# are not using SMTP. Here is an example of the configuration:
|
|
||||||
#
|
|
||||||
# config :live_beats, LiveBeats.Mailer,
|
|
||||||
# adapter: Swoosh.Adapters.Mailgun,
|
|
||||||
# api_key: System.get_env("MAILGUN_API_KEY"),
|
|
||||||
# domain: System.get_env("MAILGUN_DOMAIN")
|
|
||||||
#
|
|
||||||
# For this example you need include a HTTP client required by Swoosh API client.
|
|
||||||
# Swoosh supports Hackney and Finch out of the box:
|
|
||||||
#
|
|
||||||
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
|
|
||||||
#
|
|
||||||
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
|
|
||||||
end
|
end
|
||||||
|
|
42
fly.toml
Normal file
42
fly.toml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
app = "livebeats"
|
||||||
|
|
||||||
|
kill_signal = "SIGTERM"
|
||||||
|
kill_timeout = 5
|
||||||
|
processes = []
|
||||||
|
|
||||||
|
[deploy]
|
||||||
|
release_command = "/app/entry eval LiveBeats.Release.migrate"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
IPV6 = 1
|
||||||
|
URL_HOST = "livebeats.fly.dev"
|
||||||
|
|
||||||
|
[experimental]
|
||||||
|
allowed_public_ports = []
|
||||||
|
auto_rollback = true
|
||||||
|
|
||||||
|
[[services]]
|
||||||
|
http_checks = []
|
||||||
|
internal_port = 4000
|
||||||
|
processes = ["app"]
|
||||||
|
protocol = "tcp"
|
||||||
|
script_checks = []
|
||||||
|
|
||||||
|
[services.concurrency]
|
||||||
|
hard_limit = 25
|
||||||
|
soft_limit = 20
|
||||||
|
type = "connections"
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
handlers = ["http"]
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
[[services.ports]]
|
||||||
|
handlers = ["tls", "http"]
|
||||||
|
port = 443
|
||||||
|
|
||||||
|
[[services.tcp_checks]]
|
||||||
|
grace_period = "30s" # allow some time for startup
|
||||||
|
interval = "15s"
|
||||||
|
restart_limit = 0
|
||||||
|
timeout = "2s"
|
|
@ -12,6 +12,8 @@ defmodule LiveBeats.Accounts.User do
|
||||||
field :role, :string, default: "subscriber"
|
field :role, :string, default: "subscriber"
|
||||||
field :profile_tagline, :string
|
field :profile_tagline, :string
|
||||||
field :active_profile_user_id, :id
|
field :active_profile_user_id, :id
|
||||||
|
field :avatar_url, :string
|
||||||
|
field :external_homepage_url, :string
|
||||||
|
|
||||||
has_many :identities, Identity
|
has_many :identities, Identity
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ defmodule LiveBeats.Accounts.User do
|
||||||
A user changeset for github registration.
|
A user changeset for github registration.
|
||||||
"""
|
"""
|
||||||
def github_registration_changeset(info, primary_email, emails, token) do
|
def github_registration_changeset(info, primary_email, emails, token) do
|
||||||
%{"login" => username} = info
|
%{"login" => username, "avatar_url" => avatar_url, "html_url" => external_homepage_url} = info
|
||||||
|
|
||||||
identity_changeset =
|
identity_changeset =
|
||||||
Identity.github_registration_changeset(info, primary_email, emails, token)
|
Identity.github_registration_changeset(info, primary_email, emails, token)
|
||||||
|
@ -31,11 +33,13 @@ defmodule LiveBeats.Accounts.User do
|
||||||
params = %{
|
params = %{
|
||||||
"username" => username,
|
"username" => username,
|
||||||
"email" => primary_email,
|
"email" => primary_email,
|
||||||
"name" => get_change(identity_changeset, :provider_name)
|
"name" => get_change(identity_changeset, :provider_name),
|
||||||
|
"avatar_url" => avatar_url,
|
||||||
|
"external_homepage_url" => external_homepage_url
|
||||||
}
|
}
|
||||||
|
|
||||||
%User{}
|
%User{}
|
||||||
|> cast(params, [:email, :name, :username])
|
|> cast(params, [:email, :name, :username, :avatar_url, :external_homepage_url])
|
||||||
|> validate_required([:email, :name, :username])
|
|> validate_required([:email, :name, :username])
|
||||||
|> validate_username()
|
|> validate_username()
|
||||||
|> validate_email()
|
|> validate_email()
|
||||||
|
|
|
@ -203,7 +203,7 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
where: s.status in [:playing],
|
where: s.status in [:playing],
|
||||||
limit: ^Keyword.fetch!(opts, :limit),
|
limit: ^Keyword.fetch!(opts, :limit),
|
||||||
order_by: [desc: s.updated_at],
|
order_by: [desc: s.updated_at],
|
||||||
select: struct(u, [:id, :username, :profile_tagline])
|
select: struct(u, [:id, :username, :profile_tagline, :avatar_url, :external_homepage_url])
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.map(&get_profile!/1)
|
|> Enum.map(&get_profile!/1)
|
||||||
|
@ -214,7 +214,13 @@ defmodule LiveBeats.MediaLibrary do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_profile!(%Accounts.User{} = user) do
|
def get_profile!(%Accounts.User{} = user) do
|
||||||
%Profile{user_id: user.id, username: user.username, tagline: user.profile_tagline}
|
%Profile{
|
||||||
|
user_id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
tagline: user.profile_tagline,
|
||||||
|
avatar_url: user.avatar_url,
|
||||||
|
external_homepage_url: user.external_homepage_url
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def owns_profile?(%Accounts.User{} = user, %Profile{} = profile) do
|
def owns_profile?(%Accounts.User{} = user, %Profile{} = profile) do
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
defmodule LiveBeats.MediaLibrary.Profile do
|
defmodule LiveBeats.MediaLibrary.Profile do
|
||||||
defstruct user_id: nil, username: nil, tagline: nil
|
defstruct user_id: nil, username: nil, tagline: nil, avatar_url: nil, external_homepage_url: nil
|
||||||
end
|
end
|
||||||
|
|
28
lib/live_beats/release.ex
Normal file
28
lib/live_beats/release.ex
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule LiveBeats.Release do
|
||||||
|
@moduledoc """
|
||||||
|
Used for executing DB release tasks when run in production without Mix
|
||||||
|
installed.
|
||||||
|
"""
|
||||||
|
@app :live_beats
|
||||||
|
|
||||||
|
def migrate do
|
||||||
|
load_app()
|
||||||
|
|
||||||
|
for repo <- repos() do
|
||||||
|
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rollback(repo, version) do
|
||||||
|
load_app()
|
||||||
|
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp repos do
|
||||||
|
Application.fetch_env!(@app, :ecto_repos)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp load_app do
|
||||||
|
Application.load(@app)
|
||||||
|
end
|
||||||
|
end
|
34
lib/live_beats_web/channels/presence.ex
Normal file
34
lib/live_beats_web/channels/presence.ex
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
defmodule LiveBeatsWeb.Presence do
|
||||||
|
@moduledoc """
|
||||||
|
Provides presence tracking to channels and processes.
|
||||||
|
|
||||||
|
See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
|
||||||
|
docs for more details.
|
||||||
|
"""
|
||||||
|
use Phoenix.Presence, otp_app: :live_beats,
|
||||||
|
pubsub_server: LiveBeats.PubSub
|
||||||
|
|
||||||
|
import Phoenix.LiveView.Helpers
|
||||||
|
import LiveBeatsWeb.LiveHelpers
|
||||||
|
|
||||||
|
def listening_now(assigns) do
|
||||||
|
~H"""
|
||||||
|
<!-- users -->
|
||||||
|
<div class="px-4 mt-6 sm:px-6 lg:px-8">
|
||||||
|
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Who's Here</h2>
|
||||||
|
<ul role="list" class="grid grid-cols-1 gap-4 sm:gap-4 sm:grid-cols-2 xl:grid-cols-5 mt-3" x-max="1">
|
||||||
|
<%= for presence <- @presences do %>
|
||||||
|
<li class="relative col-span-1 flex shadow-sm rounded-md overflow-hidden">
|
||||||
|
<.link redirect_to={profile_path(presence)} class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
|
||||||
|
<img class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600" src={presence.avatar_url} alt="">
|
||||||
|
<div class="flex-1 flex items-center justify-between text-gray-900 text-sm font-medium hover:text-gray-600 pl-3">
|
||||||
|
<%= render_slot(@title, presence) %>
|
||||||
|
</div>
|
||||||
|
</.link>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
|
@ -70,7 +70,7 @@ defmodule LiveBeatsWeb.UserAuth do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> renew_session()
|
|> renew_session()
|
||||||
|> redirect(to: "/")
|
|> redirect(to: Routes.sign_in_path(conn, :index))
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -95,7 +95,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
||||||
assigns =
|
assigns =
|
||||||
assigns
|
assigns
|
||||||
|> assign_new(:outlined, fn -> false end)
|
|> assign_new(:outlined, fn -> false end)
|
||||||
|> assign_new(:class, fn -> "w-4 h-4" end)
|
|> assign_new(:class, fn -> "w-4 h-4 inline-block" end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<%= if @outlined do %>
|
<%= if @outlined do %>
|
||||||
|
|
|
@ -12,7 +12,7 @@ defmodule LiveBeatsWeb.SignInLive do
|
||||||
<p class="mt-2 text-center text-sm text-gray-600">
|
<p class="mt-2 text-center text-sm text-gray-600">
|
||||||
Or
|
Or
|
||||||
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
|
<a href="#" class="font-medium text-indigo-600 hover:text-indigo-500">
|
||||||
start your 14-day free trial
|
listen now without signing in (TODO)
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,13 +2,20 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
use LiveBeatsWeb, :live_view
|
use LiveBeatsWeb, :live_view
|
||||||
|
|
||||||
alias LiveBeats.{Accounts, MediaLibrary, MP3Stat}
|
alias LiveBeats.{Accounts, MediaLibrary, MP3Stat}
|
||||||
alias LiveBeatsWeb.LayoutComponent
|
alias LiveBeatsWeb.{LayoutComponent, Presence}
|
||||||
alias LiveBeatsWeb.SongLive.{SongRowComponent, UploadFormComponent}
|
alias LiveBeatsWeb.SongLive.{SongRowComponent, UploadFormComponent}
|
||||||
|
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.title_bar>
|
<.title_bar>
|
||||||
|
<div>
|
||||||
|
<div class="block">
|
||||||
<%= @profile.tagline %> <%= if @owns_profile? do %>(you)<% end %>
|
<%= @profile.tagline %> <%= if @owns_profile? do %>(you)<% end %>
|
||||||
|
</div>
|
||||||
|
<.link href={@profile.external_homepage_url} _target="blank" class="block text-sm text-gray-600">
|
||||||
|
<.icon name={:code}/> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<:actions>
|
<:actions>
|
||||||
<%= if @active_profile_id == @profile.user_id do %>
|
<%= if @active_profile_id == @profile.user_id do %>
|
||||||
|
@ -32,6 +39,11 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
</:actions>
|
</:actions>
|
||||||
</.title_bar>
|
</.title_bar>
|
||||||
|
|
||||||
|
<Presence.listening_now presences={@presences}>
|
||||||
|
<:abbrev let={user}><%= String.first(user.username) %></:abbrev>
|
||||||
|
<:title let={user}><%= user.username %></:title>
|
||||||
|
</Presence.listening_now>
|
||||||
|
|
||||||
<%= for song <- if(@owns_profile?, do: @songs, else: []), id = "delete-modal-#{song.id}" do %>
|
<%= for song <- if(@owns_profile?, do: @songs, else: []), id = "delete-modal-#{song.id}" do %>
|
||||||
<.modal
|
<.modal
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -89,6 +101,7 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
owns_profile?: MediaLibrary.owns_profile?(current_user, profile)
|
owns_profile?: MediaLibrary.owns_profile?(current_user, profile)
|
||||||
)
|
)
|
||||||
|> list_songs()
|
|> list_songs()
|
||||||
|
|> assign_presences()
|
||||||
|
|
||||||
{:ok, socket, temporary_assigns: [songs: []]}
|
{:ok, socket, temporary_assigns: [songs: []]}
|
||||||
end
|
end
|
||||||
|
@ -216,4 +229,15 @@ defmodule LiveBeatsWeb.SongLive.Index do
|
||||||
defp list_songs(socket) do
|
defp list_songs(socket) do
|
||||||
assign(socket, songs: MediaLibrary.list_profile_songs(socket.assigns.profile, 50))
|
assign(socket, songs: MediaLibrary.list_profile_songs(socket.assigns.profile, 50))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp assign_presences(socket) do
|
||||||
|
# TODO
|
||||||
|
assign(socket, presences: Accounts.list_users(limit: 10))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp url_text(nil), do: ""
|
||||||
|
defp url_text(url_str) do
|
||||||
|
uri = URI.parse(url_str)
|
||||||
|
uri.host <> uri.path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
>
|
>
|
||||||
<span class="sr-only">Open user menu</span>
|
<span class="sr-only">Open user menu</span>
|
||||||
<img class="h-8 w-8 rounded-full"
|
<img class="h-8 w-8 rounded-full"
|
||||||
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
src={@current_user.avatar_url}
|
||||||
alt="">
|
alt="">
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -74,7 +74,7 @@ defmodule LiveBeatsWeb.LayoutView do
|
||||||
<span class="flex w-full justify-between items-center">
|
<span class="flex w-full justify-between items-center">
|
||||||
<span class="flex min-w-0 items-center justify-between space-x-3">
|
<span class="flex min-w-0 items-center justify-between space-x-3">
|
||||||
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
|
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
|
||||||
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=3&w=256&h=256&q=80"
|
src={@current_user.avatar_url}
|
||||||
alt="">
|
alt="">
|
||||||
<span class="flex-1 flex flex-col min-w-0">
|
<span class="flex-1 flex flex-col min-w-0">
|
||||||
<span class="text-gray-900 text-sm font-medium truncate"><%= @current_user.name %></span>
|
<span class="text-gray-900 text-sm font-medium truncate"><%= @current_user.name %></span>
|
||||||
|
|
10
mix.exs
10
mix.exs
|
@ -39,8 +39,8 @@ defmodule LiveBeats.MixProject do
|
||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:phoenix_html, "~> 3.0"},
|
{:phoenix_html, "~> 3.0"},
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
{:phoenix_live_view, path: "~/oss/phoenix_live_view", override: true},
|
# {:phoenix_live_view, path: "~/oss/phoenix_live_view", override: true},
|
||||||
# {:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "cm-sticky-live-render", override: true},
|
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "cm-sticky-live-render", override: true},
|
||||||
{:floki, ">= 0.30.0", only: :test},
|
{:floki, ">= 0.30.0", only: :test},
|
||||||
{:phoenix_live_dashboard, "~> 0.5"},
|
{:phoenix_live_dashboard, "~> 0.5"},
|
||||||
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev},
|
||||||
|
@ -51,7 +51,8 @@ defmodule LiveBeats.MixProject do
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:plug_cowboy, "~> 2.5"},
|
{:plug_cowboy, "~> 2.5"},
|
||||||
{:mint, "~> 1.0"},
|
{:mint, "~> 1.0"},
|
||||||
{:heroicons, "~> 0.2.2"}
|
{:heroicons, "~> 0.2.2"},
|
||||||
|
{:castore, "~> 0.1.13"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,12 +64,11 @@ defmodule LiveBeats.MixProject do
|
||||||
# See the documentation for `Mix` for more info on aliases.
|
# See the documentation for `Mix` for more info on aliases.
|
||||||
defp aliases do
|
defp aliases do
|
||||||
[
|
[
|
||||||
setup: ["deps.get", "ecto.setup", "cmd --cd assets npm install"],
|
setup: ["deps.get", "ecto.setup"],
|
||||||
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
||||||
"assets.deploy": [
|
"assets.deploy": [
|
||||||
"cmd --cd assets npm run deploy",
|
|
||||||
"esbuild default --minify",
|
"esbuild default --minify",
|
||||||
"phx.digest"
|
"phx.digest"
|
||||||
]
|
]
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,5 +1,5 @@
|
||||||
%{
|
%{
|
||||||
"castore": {:hex, :castore, "0.1.11", "c0665858e0e1c3e8c27178e73dffea699a5b28eb72239a3b2642d208e8594914", [:mix], [], "hexpm", "91b009ba61973b532b84f7c09ce441cba7aa15cb8b006cf06c6f4bba18220081"},
|
"castore": {:hex, :castore, "0.1.13", "ccf3ab251ffaebc4319f41d788ce59a6ab3f42b6c27e598ad838ffecee0b04f9", [:mix], [], "hexpm", "a14a7eecfec7e20385493dbb92b0d12c5d77ecfd6307de10102d58c94e8c49c0"},
|
||||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
|
"phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
|
||||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"},
|
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"},
|
||||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||||
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "fba1ff66483c20d1c163ee9cd3aa74024d196f17", [branch: "cm-sticky-live-render"]},
|
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "0cb64f7b6082dc07e488d5547a35c32f8af18650", [branch: "cm-sticky-live-render"]},
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||||
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
|
||||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule LiveBeats.Repo.Migrations.AddAvatarUrlToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :avatar_url, :string
|
||||||
|
add :external_homepage_url, :string
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
6
rel/env.bat.eex
Normal file
6
rel/env.bat.eex
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
@echo off
|
||||||
|
rem Set the release to work across nodes. If using the long name format like
|
||||||
|
rem the one below (my_app@127.0.0.1), you need to also uncomment the
|
||||||
|
rem RELEASE_DISTRIBUTION variable below. Must be "sname", "name" or "none".
|
||||||
|
rem set RELEASE_DISTRIBUTION=name
|
||||||
|
rem set RELEASE_NODE=<%= @release.name %>@127.0.0.1
|
6
rel/env.sh.eex
Normal file
6
rel/env.sh.eex
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ip=$(grep fly-local-6pn /etc/hosts | cut -f 1)
|
||||||
|
export RELEASE_DISTRIBUTION=name
|
||||||
|
export RELEASE_NODE=$FLY_APP_NAME@$ip
|
||||||
|
export ELIXIR_ERL_OPTIONS="-proto_dist inet6_tcp"
|
11
rel/remote.vm.args.eex
Normal file
11
rel/remote.vm.args.eex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## Customize flags given to the VM: https://erlang.org/doc/man/erl.html
|
||||||
|
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
|
||||||
|
|
||||||
|
## Number of dirty schedulers doing IO work (file, sockets, and others)
|
||||||
|
##+SDio 5
|
||||||
|
|
||||||
|
## Increase number of concurrent ports/sockets
|
||||||
|
##+Q 65536
|
||||||
|
|
||||||
|
## Tweak GC to run more often
|
||||||
|
##-env ERL_FULLSWEEP_AFTER 10
|
11
rel/vm.args.eex
Normal file
11
rel/vm.args.eex
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
## Customize flags given to the VM: https://erlang.org/doc/man/erl.html
|
||||||
|
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
|
||||||
|
|
||||||
|
## Number of dirty schedulers doing IO work (file, sockets, and others)
|
||||||
|
##+SDio 5
|
||||||
|
|
||||||
|
## Increase number of concurrent ports/sockets
|
||||||
|
##+Q 65536
|
||||||
|
|
||||||
|
## Tweak GC to run more often
|
||||||
|
##-env ERL_FULLSWEEP_AFTER 10
|
Loading…
Reference in a new issue