mirror of
https://github.com/fly-apps/live_beats.git
synced 2025-03-28 10:15:28 +00:00
Updates for LiveView 1.0-rc
This commit is contained in:
parent
57f5c0f142
commit
056071ef6e
38 changed files with 562 additions and 440 deletions
|
@ -70,7 +70,7 @@ RUN mix release
|
|||
# the compiled release and other runtime necessities
|
||||
FROM ${RUNNER_IMAGE}
|
||||
|
||||
RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales curl ffmpeg \
|
||||
RUN apt-get update -y && apt-get install -y libstdc++6 openssl libncurses5 locales curl ffmpeg s3fs \
|
||||
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
|
||||
|
||||
# Set the locale
|
||||
|
@ -86,9 +86,10 @@ ENV BUMBLEBEE_CACHE_DIR="/app/.bumblebee"
|
|||
|
||||
# Only copy the final release from the build stage
|
||||
COPY --from=builder --chown=nobody:root /app/_build/prod/rel/live_beats ./
|
||||
# COPY --from=builder --chown=nobody:root /app/.postgresql/ ./.postgresql
|
||||
COPY --from=builder --chown=nobody:root /app/.bumblebee/ ./.bumblebee
|
||||
|
||||
USER nobody
|
||||
USER root
|
||||
|
||||
# Set the runtime ENV
|
||||
ENV ECTO_IPV6="true"
|
||||
|
|
|
@ -82,16 +82,25 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
tbody.phx-click-loading { animation: none; }
|
||||
|
||||
.phx-click-loading {
|
||||
opacity: 0.5;
|
||||
transition: opacity 1s ease-out;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.phx-loading{
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
|
||||
.phx-modal {
|
||||
opacity: 1!important;
|
||||
position: fixed;
|
||||
|
|
|
@ -3,6 +3,7 @@ import {Socket} from "phoenix"
|
|||
import {LiveSocket} from "phoenix_live_view"
|
||||
import topbar from "../vendor/topbar"
|
||||
import Sortable from "../vendor/sortable"
|
||||
import phxFeedbackDom from "./phx_feedback_dom"
|
||||
|
||||
let nowSeconds = () => Math.round(Date.now() / 1000)
|
||||
let rand = (min, max) => Math.floor(Math.random() * (max - min) + min)
|
||||
|
@ -199,7 +200,7 @@ Hooks.Ping = {
|
|||
this.handleEvent("pong", () => {
|
||||
let rtt = Date.now() - this.nowMs
|
||||
this.el.innerText = `ping: ${rtt}ms`
|
||||
// this.timer = setTimeout(() => this.ping(rtt), 1000)
|
||||
this.timer = setTimeout(() => this.ping(rtt), 5000)
|
||||
})
|
||||
this.ping(null)
|
||||
},
|
||||
|
@ -277,15 +278,16 @@ let Focus = {
|
|||
|
||||
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
|
||||
let liveSocket = new LiveSocket("/live", Socket, {
|
||||
timeout: 20000,
|
||||
hooks: Hooks,
|
||||
params: {_csrf_token: csrfToken},
|
||||
dom: {
|
||||
dom: phxFeedbackDom({
|
||||
onNodeAdded(node){
|
||||
if(node instanceof HTMLElement && node.autofocus){
|
||||
node.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let routeUpdated = () => {
|
||||
|
@ -300,7 +302,8 @@ window.addEventListener("phx:page-loading-stop", info => topbar.hide())
|
|||
// Accessible routing
|
||||
window.addEventListener("phx:page-loading-stop", routeUpdated)
|
||||
|
||||
window.addEventListener("js:exec", e => e.target[e.detail.call](...e.detail.args))
|
||||
window.addEventListener("phx:js:exec", e => liveSocket.execJS(liveSocket.main.el, e.detail.cmd))
|
||||
window.addEventListener("js:call", e => e.target[e.detail.call](...e.detail.args))
|
||||
window.addEventListener("js:focus", e => {
|
||||
let parent = document.querySelector(e.detail.parent)
|
||||
if(parent && isVisible(parent)){ e.target.focus() }
|
||||
|
|
48
assets/js/phx_feedback_dom.js
Normal file
48
assets/js/phx_feedback_dom.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
// maintain backwards compatibility of phx-feedback-for, which was removed in LiveView 1.0
|
||||
// find all phx-feedbck-for containers and show/hide phx-no-feedback class based on used inputs
|
||||
import {isUsedInput} from "phoenix_live_view"
|
||||
|
||||
let resetFeedbacks = (container, feedbacks) => {
|
||||
feedbacks = feedbacks || Array.from(container.querySelectorAll("[phx-feedback-for]"))
|
||||
.map(el => [el, el.getAttribute("phx-feedback-for")])
|
||||
|
||||
feedbacks.forEach(([feedbackEl, name]) => {
|
||||
let query = `[name="${name}"], [name="${name}[]"]`
|
||||
let isUsed = Array.from(container.querySelectorAll(query)).find(input => isUsedInput(input))
|
||||
if(isUsed || !feedbackEl.hasAttribute("phx-feedback-for")){
|
||||
feedbackEl.classList.remove("phx-no-feedback")
|
||||
} else {
|
||||
feedbackEl.classList.add("phx-no-feedback")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default phxFeedbackDom = (dom) => {
|
||||
window.addEventListener("reset", e => resetFeedbacks(document))
|
||||
let feedbacks
|
||||
// extend provided dom options with our own.
|
||||
// accumulate phx-feedback-for containers for each patch and reset feedbacks when patch ends
|
||||
return {
|
||||
onPatchStart(container){
|
||||
feedbacks = []
|
||||
dom.onPatchStart && dom.onPatchStart(container)
|
||||
},
|
||||
onNodeAdded(node){
|
||||
if(node.hasAttribute && node.hasAttribute("phx-feedback-for")){
|
||||
feedbacks.push([node, node.getAttribute("phx-feedback-for")])
|
||||
}
|
||||
dom.onNodeAdded && dom.onNodeAdded(node)
|
||||
},
|
||||
onBeforeElUpdated(from, to){
|
||||
let fromFor = from.getAttribute("phx-feedback-for")
|
||||
let toFor = to.getAttribute("phx-feedback-for")
|
||||
if(fromFor || toFor){ feedbacks.push([from, fromFor || toFor], [to, toFor || fromFor]) }
|
||||
|
||||
dom.onBeforeElUpdated && dom.onBeforeElUpdated(from, to)
|
||||
},
|
||||
onPatchEnd(container){
|
||||
resetFeedbacks(container, feedbacks)
|
||||
dom.onPatchEnd && dom.onPatchEnd(container)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,29 @@
|
|||
// See the Tailwind configuration guide for advanced usage
|
||||
// https://tailwindcss.com/docs/configuration
|
||||
const plugin = require("tailwindcss/plugin")
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./js/**/*.js',
|
||||
'../lib/*_web.ex',
|
||||
'../lib/*_web/**/*.*ex'
|
||||
"./js/**/*.js",
|
||||
"../lib/*_web.ex",
|
||||
"../lib/*_web/**/*.*ex"
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')
|
||||
require('@tailwindcss/forms'),
|
||||
// Allows prefixing tailwind classes with LiveView classes to add rules
|
||||
// only when LiveView classes are applied, for example:
|
||||
//
|
||||
// <div class="phx-click-loading:animate-ping">
|
||||
//
|
||||
plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&"])),
|
||||
plugin(({addVariant}) => addVariant("phxp-click-loading", [".phx-click-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-submitter-loading", [".phx-submit-loading&"])),
|
||||
plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])),
|
||||
plugin(({addVariant}) => addVariant("phx-error", [".phx-error&", ".phx-error &"]))
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import Config
|
||||
|
||||
config :live_beats,
|
||||
replica: LiveBeats.ReplicaRepo,
|
||||
ecto_repos: [LiveBeats.Repo]
|
||||
|
||||
config :live_beats, :files, admin_usernames: []
|
||||
|
@ -27,7 +26,7 @@ config :live_beats, LiveBeatsWeb.Endpoint,
|
|||
config :esbuild,
|
||||
version: "0.12.18",
|
||||
default: [
|
||||
args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/static/assets),
|
||||
args: ~w(js/app.js --bundle --outdir=../priv/static/assets),
|
||||
cd: Path.expand("../assets", __DIR__),
|
||||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
|
||||
]
|
||||
|
|
|
@ -2,7 +2,7 @@ import Config
|
|||
|
||||
config :live_beats, :files,
|
||||
uploads_dir: Path.expand("../priv/uploads", __DIR__),
|
||||
host: [scheme: "http", host: "localhost", port: 4001],
|
||||
host: [scheme: "http", host: "localhost", port: 4000],
|
||||
server_ip: "127.0.0.1",
|
||||
hostname: "localhost",
|
||||
transport_opts: []
|
||||
|
@ -13,23 +13,16 @@ config :live_beats, :github,
|
|||
|
||||
# Configure your database
|
||||
config :live_beats, LiveBeats.Repo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "live_beats_dev",
|
||||
username: "root",
|
||||
password: nil,
|
||||
hostname: "localhost",
|
||||
port: 26257,
|
||||
database: "live_beats_dev",
|
||||
migration_lock: false,
|
||||
stacktrace: true,
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool_size: 10
|
||||
|
||||
# Configure your replica database
|
||||
config :live_beats, LiveBeats.ReplicaRepo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "live_beats_dev",
|
||||
hostname: "localhost",
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool_size: 10,
|
||||
priv: "priv/repo"
|
||||
|
||||
# For development, we disable any cache and enable
|
||||
# debugging and code reloading.
|
||||
#
|
||||
|
|
|
@ -12,6 +12,22 @@ if System.get_env("PHX_SERVER") && System.get_env("RELEASE_NAME") do
|
|||
end
|
||||
|
||||
if config_env() == :prod do
|
||||
config :flame, :terminator, log: :info
|
||||
config :flame, :backend, FLAME.FlyBackend
|
||||
config :flame, FLAME.FlyBackend, cpu_kind: "performance", cpus: 4, memory_mb: 8192
|
||||
|
||||
config :flame, FLAME.FlyBackend,
|
||||
token: System.get_env("FLY_API_TOKEN"),
|
||||
memory_mb: 8192,
|
||||
env: %{
|
||||
"BUCKET_MOUNT" => System.get_env("BUCKET_MOUNT"),
|
||||
"DATABASE_URL" => System.get_env("DATABASE_URL"),
|
||||
"LIVE_BEATS_GITHUB_CLIENT_ID" => System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_ID"),
|
||||
"LIVE_BEATS_GITHUB_CLIENT_SECRET" => System.get_env("LIVE_BEATS_GITHUB_CLIENT_SECRET")
|
||||
}
|
||||
|
||||
config :live_beats, dns_cluster_query: System.get_env("DNS_CLUSTER_QUERY")
|
||||
|
||||
database_url =
|
||||
System.get_env("DATABASE_URL") ||
|
||||
raise """
|
||||
|
@ -19,27 +35,20 @@ if config_env() == :prod do
|
|||
For example: ecto://USER:PASS@HOST/DATABASE
|
||||
"""
|
||||
|
||||
replica_database_url = System.get_env("REPLICA_DATABASE_URL") || database_url
|
||||
|
||||
maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
|
||||
host = System.get_env("PHX_HOST") || "example.com"
|
||||
ecto_ipv6? = System.get_env("ECTO_IPV6") == "true"
|
||||
|
||||
app_name =
|
||||
System.get_env("FLY_APP_NAME") ||
|
||||
raise "FLY_APP_NAME not available"
|
||||
|
||||
config :live_beats, LiveBeats.Repo,
|
||||
# ssl: true,
|
||||
socket_options: if(ecto_ipv6?, do: [:inet6], else: []),
|
||||
migration_lock: false,
|
||||
timeout: 60_000,
|
||||
url: database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||
|
||||
config :live_beats, LiveBeats.ReplicaRepo,
|
||||
# ssl: true,
|
||||
priv: "priv/repo",
|
||||
socket_options: if(ecto_ipv6?, do: [:inet6], else: []),
|
||||
url: replica_database_url,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||
queue_interval: 60_000,
|
||||
socket_options: maybe_ipv6,
|
||||
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "5")
|
||||
|
||||
secret_key_base =
|
||||
System.get_env("SECRET_KEY_BASE") ||
|
||||
|
@ -58,13 +67,14 @@ if config_env() == :prod do
|
|||
ip: {0, 0, 0, 0, 0, 0, 0, 0},
|
||||
port: String.to_integer(System.get_env("PORT") || "4000")
|
||||
],
|
||||
secret_key_base: secret_key_base
|
||||
secret_key_base: secret_key_base,
|
||||
check_origin: false
|
||||
|
||||
config :live_beats, :files,
|
||||
admin_usernames: ~w(chrismccord mrkurt),
|
||||
uploads_dir: "/app/uploads",
|
||||
uploads_dir: Path.join(System.fetch_env!("BUCKET_MOUNT"), "/app/uploads"),
|
||||
host: [scheme: "https", host: host, port: 443],
|
||||
server_ip: System.fetch_env!("LIVE_BEATS_SERVER_IP"),
|
||||
server_ip: System.get_env("LIVE_BEATS_SERVER_IP"),
|
||||
hostname: "livebeats.local",
|
||||
transport_opts: [inet6: true]
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import Config
|
||||
|
||||
config :live_beats,
|
||||
replica: LiveBeats.Repo
|
||||
|
||||
config :live_beats, :files,
|
||||
uploads_dir: Path.expand("../tmp/test-uploads", __DIR__),
|
||||
host: [scheme: "http", host: "localhost", port: 4000],
|
||||
|
@ -21,15 +18,6 @@ config :live_beats, LiveBeats.Repo,
|
|||
pool: Ecto.Adapters.SQL.Sandbox,
|
||||
pool_size: 10
|
||||
|
||||
config :live_beats, LiveBeats.ReplicaRepo,
|
||||
username: "postgres",
|
||||
password: "postgres",
|
||||
database: "live_beats_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||
hostname: "localhost",
|
||||
show_sensitive_data_on_connection_error: true,
|
||||
pool_size: 10,
|
||||
priv: "priv/repo"
|
||||
|
||||
# We don't run a server during test. If one is required,
|
||||
# you can enable the server option below.
|
||||
config :live_beats, LiveBeatsWeb.Endpoint,
|
||||
|
|
7
fly.toml
7
fly.toml
|
@ -4,7 +4,7 @@
|
|||
#
|
||||
|
||||
app = "livebeats"
|
||||
primary_region = "ord"
|
||||
primary_region = "iad"
|
||||
kill_signal = "SIGTERM"
|
||||
kill_timeout = "5s"
|
||||
|
||||
|
@ -15,13 +15,10 @@ kill_timeout = "5s"
|
|||
release_command = "/app/bin/migrate"
|
||||
|
||||
[env]
|
||||
BUCKET_MOUNT = "/tigris"
|
||||
BUMBLEBEE_CACHE_DIR = "/app/.bumblebee"
|
||||
PHX_HOST = "livebeats.fly.dev"
|
||||
|
||||
[mounts]
|
||||
source="data"
|
||||
destination="/app/uploads"
|
||||
|
||||
[[services]]
|
||||
protocol = "tcp"
|
||||
internal_port = 4000
|
||||
|
|
|
@ -18,15 +18,15 @@ defmodule LiveBeats.Accounts do
|
|||
defp topic(user_id), do: "user:#{user_id}"
|
||||
|
||||
def list_users(opts) do
|
||||
Repo.replica().all(from u in User, limit: ^Keyword.fetch!(opts, :limit))
|
||||
Repo.all(from u in User, limit: ^Keyword.fetch!(opts, :limit))
|
||||
end
|
||||
|
||||
def get_users_map(user_ids) when is_list(user_ids) do
|
||||
Repo.replica().all(from u in User, where: u.id in ^user_ids, select: {u.id, u})
|
||||
Repo.all(from u in User, where: u.id in ^user_ids, select: {u.id, u})
|
||||
end
|
||||
|
||||
def lists_users_by_active_profile(id, opts) do
|
||||
Repo.replica().all(
|
||||
Repo.all(
|
||||
from u in User, where: u.active_profile_user_id == ^id, limit: ^Keyword.fetch!(opts, :limit)
|
||||
)
|
||||
end
|
||||
|
@ -84,11 +84,16 @@ defmodule LiveBeats.Accounts do
|
|||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_user!(id), do: Repo.replica().get!(User, id)
|
||||
def get_user!(id) do
|
||||
get_user(id) ||
|
||||
raise Ecto.NoResultsError, queryable: from(u in User, where: u.id == ^id, limit: 1)
|
||||
end
|
||||
|
||||
def get_user(id), do: Repo.replica().get(User, id)
|
||||
def get_user(id) do
|
||||
Repo.one(from(u in User, where: u.id == ^id, limit: 1))
|
||||
end
|
||||
|
||||
def get_user_by!(fields), do: Repo.replica().get_by!(User, fields)
|
||||
def get_user_by!(fields), do: Repo.get_by!(User, fields)
|
||||
|
||||
def update_active_profile(%User{active_profile_user_id: same_id} = current_user, same_id) do
|
||||
current_user
|
||||
|
|
|
@ -65,6 +65,7 @@ defmodule LiveBeats.Accounts.User do
|
|||
|> validate_required([:email])
|
||||
|> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
|
||||
|> validate_length(:email, max: 160)
|
||||
|> update_change(:email, &String.downcase/1)
|
||||
|> unsafe_validate_unique(:email, LiveBeats.Repo)
|
||||
|> unique_constraint(:email)
|
||||
end
|
||||
|
|
|
@ -19,32 +19,39 @@ defmodule LiveBeats.Application do
|
|||
|
||||
@impl true
|
||||
def start(_type, _args) do
|
||||
parent = FLAME.Parent.get()
|
||||
LiveBeats.MediaLibrary.attach()
|
||||
topologies = Application.get_env(:libcluster, :topologies) || []
|
||||
|
||||
children =
|
||||
[
|
||||
{Nx.Serving, name: WhisperServing, serving: load_serving()},
|
||||
{Cluster.Supervisor, [topologies, [name: LiveBeats.ClusterSupervisor]]},
|
||||
{Nx.Serving, name: LiveBeats.WhisperServing, serving: load_serving()},
|
||||
!parent && {DNSCluster, query: Application.get_env(:wps, :dns_cluster_query) || :ignore},
|
||||
{Task.Supervisor, name: LiveBeats.TaskSupervisor},
|
||||
{Task.Supervisor, name: Fly.Machine.TaskSupervisor},
|
||||
# Start the Ecto repository
|
||||
LiveBeats.Repo,
|
||||
LiveBeats.ReplicaRepo,
|
||||
# Start the Telemetry supervisor
|
||||
LiveBeatsWeb.Telemetry,
|
||||
# Start the PubSub system
|
||||
{Phoenix.PubSub, name: LiveBeats.PubSub},
|
||||
# start presence
|
||||
LiveBeatsWeb.Presence,
|
||||
!parent && LiveBeatsWeb.Presence,
|
||||
{Finch, name: LiveBeats.Finch},
|
||||
{FLAME.Pool,
|
||||
name: LiveBeats.WhisperRunner,
|
||||
min: 0,
|
||||
max: 5,
|
||||
max_concurrency: 10,
|
||||
min_idle_shutdown_after: :timer.seconds(30),
|
||||
idle_shutdown_after: :timer.seconds(30),
|
||||
log: :info},
|
||||
# Start the Endpoint (http/https)
|
||||
LiveBeatsWeb.Endpoint,
|
||||
!parent && LiveBeatsWeb.Endpoint,
|
||||
# Expire songs every six hours
|
||||
{LiveBeats.SongsCleaner, interval: {3600 * 6, :second}}
|
||||
!parent && {LiveBeats.SongsCleaner, interval: {3600 * 6, :second}}
|
||||
# Start a worker by calling: LiveBeats.Worker.start_link(arg)
|
||||
# {LiveBeats.Worker, arg}
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||
# for other strategies and supported options
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule LiveBeats.Audio do
|
|||
fn ss ->
|
||||
args = ~w(-ac 1 -ar 16k -f f32le -ss #{ss} -t #{chunk_time} -v quiet -)
|
||||
{data, 0} = System.cmd("ffmpeg", ["-i", path] ++ args)
|
||||
{ss, Nx.Serving.batched_run(WhisperServing, Nx.from_binary(data, :f32))}
|
||||
{ss, Nx.Serving.batched_run({:local, LiveBeats.WhisperServing}, Nx.from_binary(data, :f32))}
|
||||
end,
|
||||
max_concurrency: 2,
|
||||
timeout: :infinity
|
||||
|
|
|
@ -145,7 +145,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
def store_mp3(%Song{} = song, tmp_path) do
|
||||
File.mkdir_p!(Path.dirname(song.mp3_filepath))
|
||||
File.cp!(tmp_path, song.mp3_filepath)
|
||||
{:ok, _} = File.copy(tmp_path, song.mp3_filepath)
|
||||
end
|
||||
|
||||
def put_stats(%Ecto.Changeset{} = changeset, %MP3Stat{} = stat) do
|
||||
|
@ -160,9 +160,6 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
def import_songs(%Accounts.User{} = user, changesets, consume_file)
|
||||
when is_map(changesets) and is_function(consume_file, 2) do
|
||||
# refetch user for fresh song count
|
||||
user = Accounts.get_user!(user.id)
|
||||
|
||||
multi =
|
||||
Ecto.Multi.new()
|
||||
|> lock_playlist(user.id)
|
||||
|
@ -179,19 +176,24 @@ defmodule LiveBeats.MediaLibrary do
|
|||
chset
|
||||
|> Song.put_user(user)
|
||||
|> Song.put_mp3_path()
|
||||
|> Song.put_server_ip()
|
||||
|> Ecto.Changeset.put_change(:position, pos_start + i + 1)
|
||||
end)
|
||||
end)
|
||||
|> Ecto.Multi.run(:valid_songs_count, fn _repo, changes ->
|
||||
|> Ecto.Multi.run(:valid_songs_count, fn repo, changes ->
|
||||
new_songs_count = changes |> Enum.filter(&match?({{:song, _ref}, _}, &1)) |> Enum.count()
|
||||
validate_songs_limit(user.songs_count, new_songs_count)
|
||||
|
||||
songs_count =
|
||||
repo.one(
|
||||
from(u in Accounts.User, where: u.id == ^user.id, select: u.songs_count, limit: 1)
|
||||
)
|
||||
|
||||
validate_songs_limit(songs_count, new_songs_count)
|
||||
end)
|
||||
|> Ecto.Multi.update_all(
|
||||
:update_songs_count,
|
||||
fn %{valid_songs_count: new_count} ->
|
||||
from(u in Accounts.User,
|
||||
where: u.id == ^user.id and u.songs_count == ^user.songs_count,
|
||||
where: u.id == ^user.id,
|
||||
update: [inc: [songs_count: ^new_count]]
|
||||
)
|
||||
end,
|
||||
|
@ -233,17 +235,18 @@ defmodule LiveBeats.MediaLibrary do
|
|||
end
|
||||
|
||||
defp async_transcribe(%Song{} = song, %Accounts.User{} = user) do
|
||||
Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn ->
|
||||
# Task.Supervisor.start_child(LiveBeats.TaskSupervisor, fn ->
|
||||
FLAME.cast(LiveBeats.WhisperRunner, fn ->
|
||||
segments =
|
||||
LiveBeats.Audio.speech_to_text(song.mp3_filepath, 20, fn ss, text ->
|
||||
segment = %Song.TranscriptSegment{ss: ss, text: text}
|
||||
segment = %Song.Transcript.Segment{ss: ss, text: text}
|
||||
broadcast!(user.id, {segment, song.id})
|
||||
|
||||
segment
|
||||
end)
|
||||
|
||||
Repo.update_all(from(s in Song, where: s.id == ^song.id),
|
||||
set: [transcript_segments: segments]
|
||||
set: [transcript: %Song.Transcript{segments: segments}]
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
@ -267,7 +270,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
end
|
||||
|
||||
def list_genres do
|
||||
Repo.replica().all(Genre, order_by: [asc: :title])
|
||||
Repo.all(Genre, order_by: [asc: :title])
|
||||
end
|
||||
|
||||
def list_profile_songs(%Profile{} = profile, limit \\ 100) do
|
||||
|
@ -276,7 +279,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
limit: ^limit
|
||||
)
|
||||
|> order_by_playlist(:asc)
|
||||
|> Repo.replica().all()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def list_active_profiles(opts) do
|
||||
|
@ -288,13 +291,13 @@ defmodule LiveBeats.MediaLibrary do
|
|||
order_by: [desc: s.updated_at],
|
||||
select: struct(u, [:id, :username, :profile_tagline, :avatar_url, :external_homepage_url])
|
||||
)
|
||||
|> Repo.replica().all()
|
||||
|> Repo.all()
|
||||
|> Enum.map(&get_profile!/1)
|
||||
end
|
||||
|
||||
def get_current_active_song(%Profile{user_id: user_id}) do
|
||||
Repo.replica().one(
|
||||
from(s in Song, where: s.user_id == ^user_id and s.status in [:playing, :paused])
|
||||
Repo.one(
|
||||
from(s in Song, where: s.user_id == ^user_id and s.status in [:playing, :paused], limit: 1)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -330,7 +333,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
end
|
||||
end
|
||||
|
||||
def get_song!(id), do: Repo.replica().get!(Song, id)
|
||||
def get_song!(id), do: Repo.get!(Song, id)
|
||||
|
||||
def get_first_song(%Profile{user_id: user_id}) do
|
||||
from(s in Song,
|
||||
|
@ -338,7 +341,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
limit: 1
|
||||
)
|
||||
|> order_by_playlist(:asc)
|
||||
|> Repo.replica().one()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_last_song(%Profile{user_id: user_id}) do
|
||||
|
@ -347,7 +350,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
limit: 1
|
||||
)
|
||||
|> order_by_playlist(:desc)
|
||||
|> Repo.replica().one()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_next_song(%Song{} = song, %Profile{} = profile) do
|
||||
|
@ -357,7 +360,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
limit: 1
|
||||
)
|
||||
|> order_by_playlist(:asc)
|
||||
|> Repo.replica().one()
|
||||
|> Repo.one()
|
||||
|
||||
next || get_first_song(profile)
|
||||
end
|
||||
|
@ -369,7 +372,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
limit: 1
|
||||
)
|
||||
|> order_by_playlist(:desc)
|
||||
|> Repo.replica().one()
|
||||
|> Repo.one()
|
||||
|
||||
prev || get_last_song(profile)
|
||||
end
|
||||
|
@ -379,7 +382,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
multi =
|
||||
Ecto.Multi.new()
|
||||
|> lock_playlist(song.user_id)
|
||||
# |> lock_playlist(song.user_id)
|
||||
|> Ecto.Multi.run(:index, fn repo, _changes ->
|
||||
case repo.one(from(s in Song, where: s.user_id == ^song.user_id, select: count(s.id))) do
|
||||
count when new_index < count -> {:ok, new_index}
|
||||
|
@ -409,7 +412,10 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
case LiveBeats.Repo.transaction(multi) do
|
||||
{:ok, _} ->
|
||||
broadcast!(song.user_id, %Events.NewPosition{song: %Song{song | position: new_index}})
|
||||
broadcast_from!(self(), song.user_id, %Events.NewPosition{
|
||||
song: %Song{song | position: new_index}
|
||||
})
|
||||
|
||||
:ok
|
||||
|
||||
{:error, failed_op, _failed_val, _changes} ->
|
||||
|
@ -428,7 +434,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
old_index = song.position
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> lock_playlist(song.user_id)
|
||||
# |> lock_playlist(song.user_id)
|
||||
|> Ecto.Multi.delete(:delete, song)
|
||||
|> multi_update_all(:dec_positions, fn _ ->
|
||||
from(s in Song,
|
||||
|
@ -451,7 +457,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
|
||||
def expire_songs_older_than(count, interval) when interval in [:month, :day, :second] do
|
||||
admin_usernames = LiveBeats.config([:files, :admin_usernames])
|
||||
server_ip = LiveBeats.config([:files, :server_ip])
|
||||
# server_ip = LiveBeats.config([:files, :server_ip])
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.delete_all(
|
||||
|
@ -459,7 +465,7 @@ defmodule LiveBeats.MediaLibrary do
|
|||
from(s in Song,
|
||||
join: u in assoc(s, :user),
|
||||
where: s.inserted_at < from_now(^(-count), ^to_string(interval)),
|
||||
where: s.server_ip == ^server_ip,
|
||||
# where: s.server_ip == ^server_ip,
|
||||
where: u.username not in ^admin_usernames,
|
||||
select: %{user_id: s.user_id, mp3_filepath: s.mp3_filepath}
|
||||
)
|
||||
|
@ -543,6 +549,10 @@ defmodule LiveBeats.MediaLibrary do
|
|||
Phoenix.PubSub.broadcast!(@pubsub, topic(user_id), {__MODULE__, msg})
|
||||
end
|
||||
|
||||
defp broadcast_from!(pid, user_id, msg) when is_integer(user_id) do
|
||||
Phoenix.PubSub.broadcast_from!(@pubsub, pid, topic(user_id), {__MODULE__, msg})
|
||||
end
|
||||
|
||||
defp topic(user_id) when is_integer(user_id), do: "profile:#{user_id}"
|
||||
|
||||
defp validate_songs_limit(user_songs, new_songs_count) do
|
||||
|
@ -558,6 +568,9 @@ defmodule LiveBeats.MediaLibrary do
|
|||
end
|
||||
|
||||
defp lock_playlist(%Ecto.Multi{} = multi, user_id) do
|
||||
Repo.multi_transaction_lock(multi, :playlist, user_id)
|
||||
Ecto.Multi.run(multi, :playlist_lock, fn repo, _changes ->
|
||||
repo.all(from(u in "users", where: u.id == ^user_id, select: u.id, lock: "FOR UPDATE"))
|
||||
{:ok, user_id}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,14 +20,16 @@ defmodule LiveBeats.MediaLibrary.Song do
|
|||
field :mp3_filepath, :string
|
||||
field :mp3_filename, :string
|
||||
field :mp3_filesize, :integer, default: 0
|
||||
field :server_ip, EctoNetwork.INET
|
||||
field :server_ip, :string
|
||||
field :position, :integer, default: 0
|
||||
belongs_to :user, Accounts.User
|
||||
belongs_to :genre, LiveBeats.MediaLibrary.Genre
|
||||
|
||||
embeds_many :transcript_segments, TranscriptSegment do
|
||||
field :ss, :integer
|
||||
field :text, :string
|
||||
embeds_one :transcript, Transcript do
|
||||
embeds_many :segments, Segment do
|
||||
field :ss, :integer
|
||||
field :text, :string
|
||||
end
|
||||
end
|
||||
|
||||
timestamps()
|
||||
|
|
|
@ -23,6 +23,7 @@ defmodule LiveBeats.Release do
|
|||
end
|
||||
|
||||
defp load_app do
|
||||
Application.ensure_all_started(:ssl)
|
||||
Application.load(@app)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,6 @@ defmodule LiveBeats.Repo do
|
|||
otp_app: :live_beats,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
|
||||
def replica, do: LiveBeats.config([:replica])
|
||||
|
||||
@locks %{playlist: 1}
|
||||
|
||||
def multi_transaction_lock(multi, scope, id) when is_atom(scope) and is_integer(id) do
|
||||
|
@ -15,9 +13,3 @@ defmodule LiveBeats.Repo do
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule LiveBeats.ReplicaRepo do
|
||||
use Ecto.Repo,
|
||||
otp_app: :live_beats,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
end
|
||||
|
|
|
@ -14,8 +14,17 @@ defmodule LiveBeats.SongsCleaner do
|
|||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
{count, interval} = Keyword.fetch!(opts, :interval)
|
||||
{:ok, schedule_cleanup(%{count: count, interval: interval}, 0)}
|
||||
region = System.get_env("FLY_REGION")
|
||||
primary_region = System.get_env("PRIMARY_REGION")
|
||||
|
||||
case region do
|
||||
region when region in [nil, primary_region] ->
|
||||
{count, interval} = Keyword.fetch!(opts, :interval)
|
||||
{:ok, schedule_cleanup(%{count: count, interval: interval}, 0)}
|
||||
|
||||
_ ->
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
|
@ -41,6 +41,8 @@ defmodule LiveBeatsWeb do
|
|||
import Phoenix.Controller,
|
||||
only: [get_csrf_token: 0, view_module: 1, view_template: 1]
|
||||
|
||||
use Phoenix.Component
|
||||
|
||||
# Include general helpers for rendering HTML
|
||||
unquote(html_helpers())
|
||||
end
|
||||
|
@ -100,9 +102,6 @@ defmodule LiveBeatsWeb do
|
|||
# Use all HTML functionality (forms, tags, etc)
|
||||
use Phoenix.HTML
|
||||
|
||||
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
|
||||
use Phoenix.Component
|
||||
|
||||
import LiveBeatsWeb.CoreComponents
|
||||
import LiveBeatsWeb.Gettext
|
||||
alias LiveBeatsWeb.Router.Helpers, as: Routes
|
||||
|
|
|
@ -166,7 +166,7 @@ defmodule LiveBeatsWeb.Presence.BadgeComponent do
|
|||
<%= if @region do %>
|
||||
<img
|
||||
class="inline w-7 h-7 absolute right-3 top-3"
|
||||
src={"https://fly.io/ui/images/#{@region}.svg"}
|
||||
src={"https://fly.io/phx/ui/images/#{@region}.svg"}
|
||||
title={region_name(@region)}
|
||||
/>
|
||||
<% end %>
|
||||
|
|
|
@ -33,6 +33,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
<div
|
||||
id="connection-status"
|
||||
class="hidden rounded-md bg-red-50 p-4 fixed top-1 right-1 w-96 fade-in-scale z-50"
|
||||
phx-disconnected={JS.show()}
|
||||
js-show={show("#connection-status")}
|
||||
js-hide={hide("#connection-status")}
|
||||
>
|
||||
|
@ -105,7 +106,6 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
class="rounded-md bg-green-50 p-4 fixed top-1 right-1 w-96 fade-in-scale z-50"
|
||||
phx-click={JS.push("lv:clear-flash") |> JS.remove_class("fade-in-scale") |> hide("#flash")}
|
||||
phx-value-key="info"
|
||||
phx-hook="Flash"
|
||||
>
|
||||
<div class="flex justify-between items-center space-x-3 text-green-700">
|
||||
<.icon name={:check_circle} class="w-5 h-5" />
|
||||
|
@ -278,7 +278,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
{"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"}
|
||||
)
|
||||
|> JS.hide(to: "#show-mobile-sidebar", transition: "fade-out")
|
||||
|> JS.dispatch("js:exec", to: "#hide-mobile-sidebar", detail: %{call: "focus", args: []})
|
||||
|> JS.dispatch("js:call", to: "#hide-mobile-sidebar", detail: %{call: "focus", args: []})
|
||||
end
|
||||
|
||||
def hide_mobile_sidebar(js \\ %JS{}) do
|
||||
|
@ -291,7 +291,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
{"transition ease-in-out duration-300 transform", "translate-x-0", "-translate-x-full"}
|
||||
)
|
||||
|> JS.show(to: "#show-mobile-sidebar", transition: "fade-in")
|
||||
|> JS.dispatch("js:exec", to: "#show-mobile-sidebar", detail: %{call: "focus", args: []})
|
||||
|> JS.dispatch("js:call", to: "#show-mobile-sidebar", detail: %{call: "focus", args: []})
|
||||
end
|
||||
|
||||
def show(js \\ %JS{}, selector) do
|
||||
|
@ -348,6 +348,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
|
||||
def show_modal(js \\ %JS{}, id) when is_binary(id) do
|
||||
js
|
||||
|> JS.remove_attribute("disabled", to: "##{id}-confirm")
|
||||
|> JS.show(
|
||||
to: "##{id}",
|
||||
display: "inline-block",
|
||||
|
@ -360,11 +361,12 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
{"ease-out duration-300", "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
|
||||
"opacity-100 translate-y-0 sm:scale-100"}
|
||||
)
|
||||
|> js_exec("##{id}-confirm", "focus", [])
|
||||
|> js_call("##{id}-confirm", "focus", [])
|
||||
end
|
||||
|
||||
def hide_modal(js \\ %JS{}, id) do
|
||||
js
|
||||
|> JS.set_attribute({"disabled", ""}, to: "##{id}-confirm")
|
||||
|> JS.remove_class("fade-in", to: "##{id}")
|
||||
|> JS.hide(
|
||||
to: "##{id}",
|
||||
|
@ -388,7 +390,10 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
attr :rest, :global
|
||||
|
||||
slot :title
|
||||
slot :confirm
|
||||
slot :confirm do
|
||||
attr :type, :string
|
||||
attr :form, :string
|
||||
end
|
||||
slot :cancel
|
||||
|
||||
def modal(assigns) do
|
||||
|
@ -447,9 +452,8 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
<%= for confirm <- @confirm do %>
|
||||
<button
|
||||
id={"#{@id}-confirm"}
|
||||
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
class="phx-submit-loading:opacity-50 disabled:opacity-50 w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
phx-click={@on_confirm}
|
||||
phx-disable-with
|
||||
{assigns_to_attributes(confirm)}
|
||||
>
|
||||
<%= render_slot(confirm) %>
|
||||
|
@ -527,15 +531,13 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
def button(%{patch: _} = assigns) do
|
||||
~H"""
|
||||
<%= if @primary do %>
|
||||
<%= live_patch [to: @patch, class: "order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3"] ++
|
||||
Map.to_list(@rest) do %>
|
||||
<.link patch={@patch} class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3" {@rest}>
|
||||
<%= render_slot(@inner_block) %>
|
||||
<% end %>
|
||||
</.link>
|
||||
<% else %>
|
||||
<%= live_patch [to: @patch, class: "order-1 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0 lg:ml-3"] ++
|
||||
assigns_to_attributes(assigns, [:primary, :patch]) do %>
|
||||
<.link patch={@patch} class="order-1 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0 lg:ml-3" {assigns_to_attributes(assigns, [:primary, :patch])}>
|
||||
<%= render_slot(@inner_block) %>
|
||||
<% end %>
|
||||
</.link>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
@ -598,7 +600,7 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
data-drop={@sortable_drop}
|
||||
>
|
||||
<tr
|
||||
:for={{row, i} <- Enum.with_index(@rows)}
|
||||
:for={row <- @rows}
|
||||
id={@row_id && @row_id.(row)}
|
||||
phx-remove={@row_remove && @row_remove.(row)}
|
||||
class="hover:bg-gray-50"
|
||||
|
@ -606,10 +608,11 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
<td
|
||||
:for={col <- @col}
|
||||
phx-click={@row_click && @row_click.(row)}
|
||||
class={
|
||||
class={[
|
||||
"phxp-click-loading:cursor-not-allowed phxp-click-loading:pointer-events-none",
|
||||
col[:class!] ||
|
||||
"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{col[:class]}"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
<%= render_slot(col, row) %>
|
||||
|
@ -623,59 +626,17 @@ defmodule LiveBeatsWeb.CoreComponents do
|
|||
"""
|
||||
end
|
||||
|
||||
attr :id, :any, required: true
|
||||
attr :module, :atom, required: true
|
||||
attr :row_id, :any, default: false
|
||||
attr :rows, :list, required: true
|
||||
attr :owns_profile?, :boolean, default: false
|
||||
attr :active_id, :any, default: nil
|
||||
|
||||
slot :col do
|
||||
attr :label, :string
|
||||
attr :class, :string
|
||||
end
|
||||
|
||||
def live_table(assigns) do
|
||||
~H"""
|
||||
<div class="hidden mt-8 sm:block">
|
||||
<div class="align-middle inline-block min-w-full border-b border-gray-200">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr class="border-t border-gray-200">
|
||||
<%= for col <- @col do %>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<span class="lg:pl-2"><%= col.label %></span>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id={@id} class="bg-white divide-y divide-gray-100" phx-update="append">
|
||||
<%= for {row, i} <- Enum.with_index(@rows) do %>
|
||||
<.live_component
|
||||
module={@module}
|
||||
id={@row_id.(row)}
|
||||
row={row}
|
||||
col={@col}
|
||||
index={i}
|
||||
active_id={@active_id}
|
||||
class="hover:bg-gray-50"
|
||||
owns_profile?={@owns_profile?}
|
||||
/>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls a wired up event listener to call a function with arguments.
|
||||
|
||||
window.addEventListener("js:exec", e => e.target[e.detail.call](...e.detail.args))
|
||||
window.addEventListener("js:call", e => e.target[e.detail.call](...e.detail.args))
|
||||
"""
|
||||
def js_exec(js \\ %JS{}, to, call, args) do
|
||||
JS.dispatch(js, "js:exec", to: to, detail: %{call: call, args: args})
|
||||
def js_call(js \\ %JS{}, to, call, args) do
|
||||
JS.dispatch(js, "js:call", to: to, detail: %{call: call, args: args})
|
||||
end
|
||||
|
||||
def push_js_cmd(socket, %JS{ops: ops}) do
|
||||
Phoenix.LiveView.push_event(socket, "js:exec", %{cmd: Phoenix.json_library().encode!(ops)})
|
||||
end
|
||||
|
||||
def focus(js \\ %JS{}, parent, to) do
|
||||
|
|
|
@ -219,8 +219,6 @@
|
|||
Re-establishing connection...
|
||||
</.connection_status>
|
||||
|
||||
<.live_component module={LiveBeatsWeb.LayoutComponent} id="layout" />
|
||||
|
||||
<%= if @current_user do %>
|
||||
<%= live_render(@socket, LiveBeatsWeb.PlayerLive, id: "player", session: %{}, sticky: true) %>
|
||||
<% end %>
|
||||
|
@ -231,16 +229,10 @@
|
|||
<div class="relative">
|
||||
<div
|
||||
id="ping-container"
|
||||
class="fixed bottom-0 right-0 bg-gray-900 text-gray-200 px-2 rounded-tl-md text-sm w-[114px] min-w-max"
|
||||
class="fixed bottom-0 right-0 bg-gray-900 text-gray-200 px-2 rounded-tl-md text-sm w-[90px] min-w-max"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<span id="ping" phx-hook="Ping"></span>
|
||||
<%= if @region do %>
|
||||
<img
|
||||
class="inline w-5 h-5 absolute right-0"
|
||||
src={"https://fly.io/ui/images/#{@region}.svg"}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,17 @@ defmodule LiveBeatsWeb.FileController do
|
|||
require Logger
|
||||
|
||||
def show(conn, %{"id" => filename_uuid, "token" => token}) do
|
||||
case Phoenix.Token.decrypt(conn, "file", token, max_age: :timer.minutes(1)) do
|
||||
{:ok, %{vsn: 1, uuid: ^filename_uuid, size: _size}} ->
|
||||
path = MediaLibrary.local_filepath(filename_uuid)
|
||||
do_send_file(conn, path)
|
||||
|
||||
_ ->
|
||||
send_resp(conn, :unauthorized, "")
|
||||
end
|
||||
end
|
||||
|
||||
def old_show(conn, %{"id" => filename_uuid, "token" => token}) do
|
||||
path = MediaLibrary.local_filepath(filename_uuid)
|
||||
mime_type = MIME.from_path(path)
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ defmodule LiveBeatsWeb.Endpoint do
|
|||
signing_salt: "9OALgV62"
|
||||
]
|
||||
|
||||
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
||||
socket "/live", Phoenix.LiveView.Socket,
|
||||
websocket: [connect_info: [session: @session_options]],
|
||||
longpoll: [connect_info: [session: @session_options]]
|
||||
|
||||
# Serve at "/" the static files from "priv/static" directory.
|
||||
#
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
defmodule LiveBeatsWeb.LayoutComponent do
|
||||
@moduledoc """
|
||||
Component for rendering content inside layout without full DOM patch.
|
||||
"""
|
||||
use LiveBeatsWeb, :live_component
|
||||
|
||||
def show_modal(module, attrs) do
|
||||
send_update(__MODULE__, id: "layout", show: Enum.into(attrs, %{module: module}))
|
||||
end
|
||||
|
||||
def hide_modal do
|
||||
send_update(__MODULE__, id: "layout", show: nil)
|
||||
end
|
||||
|
||||
def update(%{id: id} = assigns, socket) do
|
||||
show =
|
||||
case assigns[:show] do
|
||||
%{module: _module, confirm: {text, attrs}} = show ->
|
||||
show
|
||||
|> Map.put_new(:title, show[:title])
|
||||
|> Map.put_new(:on_cancel, show[:on_cancel] || %JS{})
|
||||
|> Map.put_new(:on_confirm, show[:on_confirm] || %JS{})
|
||||
|> Map.put_new(:patch, nil)
|
||||
|> Map.put_new(:navigate, nil)
|
||||
|> Map.merge(%{confirm_text: text, confirm_attrs: attrs})
|
||||
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
|
||||
{:ok, assign(socket, id: id, show: show)}
|
||||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={unless @show, do: "hidden"}>
|
||||
<%= if @show do %>
|
||||
<.modal
|
||||
show
|
||||
id={@id}
|
||||
navigate={@show.navigate}
|
||||
patch={@show.patch}
|
||||
on_cancel={@show.on_cancel}
|
||||
on_confirm={@show.on_confirm}
|
||||
>
|
||||
<:title><%= @show.title %></:title>
|
||||
<.live_component module={@show.module} {@show} />
|
||||
<:cancel>Cancel</:cancel>
|
||||
<:confirm {@show.confirm_attrs}><%= @show.confirm_text %></:confirm>
|
||||
</.modal>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -2,13 +2,29 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
use LiveBeatsWeb, :live_view
|
||||
|
||||
alias LiveBeats.{Accounts, MediaLibrary, MP3Stat}
|
||||
alias LiveBeatsWeb.{LayoutComponent, Presence}
|
||||
alias LiveBeatsWeb.Presence
|
||||
alias LiveBeatsWeb.ProfileLive.{UploadFormComponent}
|
||||
|
||||
@max_presences 20
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.modal
|
||||
id="upload"
|
||||
patch={profile_path(@current_user)}
|
||||
on_cancel={JS.push("cancel", target: "#upload-form")}
|
||||
phx-mounted={@live_action == :new && show_modal("upload")}
|
||||
>
|
||||
<:title>Add Music</:title>
|
||||
<.live_component
|
||||
id="upload-form"
|
||||
module={UploadFormComponent}
|
||||
current_user={@current_user}
|
||||
on_complete={hide_modal("upload")}
|
||||
/>
|
||||
<:confirm type="submit" form="song-form">Save</:confirm>
|
||||
<:cancel>Cancel</:cancel>
|
||||
</.modal>
|
||||
<.title_bar>
|
||||
<div>
|
||||
<div class="block">
|
||||
|
@ -16,7 +32,9 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
<%= if @owns_profile? do %>
|
||||
(you)
|
||||
<% end %>
|
||||
– <%= ngettext("%{count} song", "%{count} songs", @songs_count) %>
|
||||
<span>
|
||||
– <%= ngettext("%{count} song", "%{count} songs", @songs_count) %>
|
||||
</span>
|
||||
</div>
|
||||
<.link href={@profile.external_homepage_url} target="_blank" class="text-sm text-gray-600">
|
||||
<.icon name={:code} /> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
|
||||
|
@ -37,7 +55,7 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
primary
|
||||
phx-click={
|
||||
JS.push("switch_profile",
|
||||
value: %{user_id: @profile.user_id},
|
||||
value: %{user_id: to_string(@profile.user_id)},
|
||||
target: "#player",
|
||||
loading: "#player"
|
||||
)
|
||||
|
@ -46,11 +64,14 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
<.icon name={:play} /><span class="ml-2">Listen</span>
|
||||
</.button>
|
||||
<% end %>
|
||||
<%= if @owns_profile? do %>
|
||||
<.button id="upload-btn" primary patch={profile_upload_path(@current_user)}>
|
||||
<.icon name={:upload} /><span class="ml-2">Upload Songs</span>
|
||||
</.button>
|
||||
<% end %>
|
||||
<.button
|
||||
:if={@owns_profile?}
|
||||
id="upload-btn"
|
||||
primary
|
||||
phx-click={show_modal("upload") |> JS.patch(profile_upload_path(@current_user))}
|
||||
>
|
||||
<.icon name={:upload} /><span class="ml-2">Upload Songs</span>
|
||||
</.button>
|
||||
</:actions>
|
||||
</.title_bar>
|
||||
|
||||
|
@ -67,89 +88,105 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dialogs" phx-update="stream">
|
||||
<%= for {_id, song} <- if(@owns_profile?, do: @streams.songs, else: []), id = "delete-modal-#{song.id}" do %>
|
||||
<.modal
|
||||
id={id}
|
||||
on_confirm={
|
||||
JS.push("delete", value: %{id: song.id})
|
||||
|> hide_modal(id)
|
||||
|> focus_closest("#song-#{song.id}")
|
||||
|> hide("#song-#{song.id}")
|
||||
}
|
||||
on_cancel={focus("##{id}", "#delete-song-#{song.id}")}
|
||||
>
|
||||
Are you sure you want to delete "<%= song.title %>"?
|
||||
<:cancel>Cancel</:cancel>
|
||||
<:confirm>Delete</:confirm>
|
||||
</.modal>
|
||||
<% end %>
|
||||
<div :if={@owns_profile?} id="dialogs" phx-update="stream">
|
||||
<.modal
|
||||
:for={{_id, song} <- @streams.songs}
|
||||
:if={song.id}
|
||||
id={"delete-modal-#{song.id}"}
|
||||
on_confirm={
|
||||
JS.push("delete", value: %{id: to_string(song.id)})
|
||||
|> hide_modal("delete-modal-#{song.id}")
|
||||
|> focus_closest("#song-#{song.id}")
|
||||
|> hide("#songs-#{song.id}")
|
||||
}
|
||||
on_cancel={focus("#delete-modal-#{song.id}", "#delete-song-#{song.id}")}
|
||||
>
|
||||
Are you sure you want to delete "<%= song.title %>"?
|
||||
<:cancel>Cancel</:cancel>
|
||||
<:confirm>Delete</:confirm>
|
||||
</.modal>
|
||||
</div>
|
||||
|
||||
<.table
|
||||
id="songs"
|
||||
rows={@streams.songs}
|
||||
row_id={fn {id, _song} -> id end}
|
||||
row_click={fn {_id, song} -> JS.push("play_or_pause", value: %{id: song.id}) end}
|
||||
streamable
|
||||
sortable_drop="row_dropped"
|
||||
>
|
||||
<:col
|
||||
:let={{_id, song}}
|
||||
label="Title"
|
||||
class!="px-6 py-3 text-sm font-medium text-gray-900 min-w-[200px] md:min-w-[20rem] cursor-pointer"
|
||||
<div>
|
||||
<.table
|
||||
id="songs"
|
||||
rows={@streams.songs}
|
||||
row_id={fn {id, _song} -> id end}
|
||||
row_click={
|
||||
fn {id, song} ->
|
||||
JS.push("play_or_pause",
|
||||
loading: "#songs tbody, ##{id}",
|
||||
value: %{id: to_string(song.id)}
|
||||
)
|
||||
end
|
||||
}
|
||||
streamable
|
||||
sortable_drop="row_dropped"
|
||||
>
|
||||
<span :if={song.status == :playing} class="flex pt-1 relative mr-2 w-4">
|
||||
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
|
||||
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1" aria-label="Playing" role="button" />
|
||||
</span>
|
||||
<span :if={song.status == :paused} class="flex pt-1 relative mr-2 w-4">
|
||||
<.icon
|
||||
name={:volume_up}
|
||||
class="h-5 w-5 -mt-1 -ml-1 text-gray-400"
|
||||
aria-label="Paused"
|
||||
role="button"
|
||||
/>
|
||||
</span>
|
||||
<span :if={song.status == :stopped} class="flex relative w-6 -translate-x-1">
|
||||
<.icon
|
||||
:if={@owns_profile?}
|
||||
name={:play}
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-label="Play"
|
||||
role="button"
|
||||
/>
|
||||
</span>
|
||||
<%= song.title %>
|
||||
</:col>
|
||||
<:col :let={{_id, song}} label="Artist"><%= song.artist %></:col>
|
||||
<:col
|
||||
:let={{_id, song}}
|
||||
label="Attribution"
|
||||
class="max-w-5xl break-words text-gray-600 font-light"
|
||||
>
|
||||
<%= song.attribution %>
|
||||
</:col>
|
||||
<:col :let={{_id, song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
|
||||
<:col :let={{_id, song}} :if={@owns_profile?} label="">
|
||||
<.link
|
||||
id={"delete-song-#{song.id}"}
|
||||
phx-click={show_modal("delete-modal-#{song.id}")}
|
||||
class="inline-flex items-center px-3 py-2 text-sm leading-4 font-medium"
|
||||
<:col
|
||||
:let={{_id, song}}
|
||||
label="Title"
|
||||
class!="px-6 py-3 text-sm font-medium text-gray-900 min-w-[200px] md:min-w-[20rem] cursor-pointer"
|
||||
>
|
||||
<.icon name={:trash} class="-ml-0.5 mr-2 h-4 w-4" /> Delete
|
||||
</.link>
|
||||
</:col>
|
||||
</.table>
|
||||
<span :if={song.status == :playing} class="flex pt-1 relative mr-2 w-4">
|
||||
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
|
||||
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1" aria-label="Playing" role="button" />
|
||||
</span>
|
||||
<span :if={song.status == :paused} class="flex pt-1 relative mr-2 w-4">
|
||||
<.icon
|
||||
name={:volume_up}
|
||||
class="h-5 w-5 -mt-1 -ml-1 text-gray-400"
|
||||
aria-label="Paused"
|
||||
role="button"
|
||||
/>
|
||||
</span>
|
||||
<span :if={song.status == :stopped} class="flex relative w-6 -translate-x-1">
|
||||
<.icon
|
||||
:if={@owns_profile?}
|
||||
name={:play}
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-label="Play"
|
||||
role="button"
|
||||
/>
|
||||
</span>
|
||||
<%= song.title %>
|
||||
</:col>
|
||||
<:col :let={{_id, song}} label="Artist"><%= song.artist %></:col>
|
||||
<:col
|
||||
:let={{_id, song}}
|
||||
label="Attribution"
|
||||
class="max-w-5xl break-words text-gray-600 font-light"
|
||||
>
|
||||
<%= song.attribution %>
|
||||
</:col>
|
||||
<:col :let={{_id, song}} label="Duration">
|
||||
<%= MP3Stat.to_mmss(song.duration) %>
|
||||
</:col>
|
||||
<:col :let={{_id, song}} :if={@owns_profile?} label="">
|
||||
<.link
|
||||
id={"delete-song-#{song.id}"}
|
||||
phx-click={show_modal("delete-modal-#{song.id}")}
|
||||
class="inline-flex items-center px-3 py-2 text-sm leading-4 font-medium"
|
||||
>
|
||||
<.icon name={:trash} class="-ml-0.5 mr-2 h-4 w-4" /> Delete
|
||||
</.link>
|
||||
</:col>
|
||||
</.table>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(%{"profile_username" => profile_username}, _session, socket) do
|
||||
%{current_user: current_user} = socket.assigns
|
||||
|
||||
profile =
|
||||
Accounts.get_user_by!(username: profile_username)
|
||||
|> MediaLibrary.get_profile!()
|
||||
profile_user =
|
||||
if current_user.username == profile_username do
|
||||
current_user
|
||||
else
|
||||
Accounts.get_user_by!(username: profile_username)
|
||||
end
|
||||
|
||||
profile = MediaLibrary.get_profile!(profile_user)
|
||||
|
||||
if connected?(socket) do
|
||||
MediaLibrary.subscribe_to_profile(profile)
|
||||
|
@ -157,48 +194,68 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
Presence.subscribe(profile)
|
||||
end
|
||||
|
||||
active_song = MediaLibrary.get_current_active_song(profile)
|
||||
segments = if active_song, do: active_song.transcript_segments, else: []
|
||||
|
||||
songs = MediaLibrary.list_profile_songs(profile, 50)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(
|
||||
active_song_id: active_song && active_song.id,
|
||||
active_song_id: nil,
|
||||
active_profile_id: current_user.active_profile_user_id,
|
||||
profile: profile,
|
||||
owns_profile?: MediaLibrary.owns_profile?(current_user, profile),
|
||||
songs_count: Enum.count(songs)
|
||||
owns_profile?: MediaLibrary.owns_profile?(current_user, profile)
|
||||
)
|
||||
|> stream(:songs, songs)
|
||||
|> stream(:transcript_segments, segments, dom_id: &"ss-#{&1.ss}")
|
||||
|> stream_configure(:transcript_segments, dom_id: &"ss-#{&1.ss}")
|
||||
|> stream(:transcript_segments, [])
|
||||
|> stream_songs()
|
||||
|> assign_presences()
|
||||
|
||||
{:ok, socket, temporary_assigns: [presences: %{}]}
|
||||
end
|
||||
|
||||
def stream_songs(socket) do
|
||||
%{profile: profile} = socket.assigns
|
||||
songs = MediaLibrary.list_profile_songs(profile, 50)
|
||||
active_song = Enum.find(songs, fn song -> song.status in [:playing, :paused] end)
|
||||
segments = if active_song, do: active_song.transcript.segments, else: []
|
||||
|
||||
socket
|
||||
|> assign(
|
||||
songs: songs,
|
||||
active_song: active_song,
|
||||
active_song_id: active_song && active_song.id,
|
||||
segments: segments,
|
||||
songs_count: Enum.count(songs)
|
||||
)
|
||||
|> stream(:songs, songs, reset: true)
|
||||
end
|
||||
|
||||
def handle_params(params, _url, socket) do
|
||||
LayoutComponent.hide_modal()
|
||||
{:noreply, socket |> apply_action(socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
def handle_event("play_or_pause", %{"id" => id}, socket) do
|
||||
%{active_song_id: active_song_id} = socket.assigns
|
||||
song = MediaLibrary.get_song!(id)
|
||||
can_playback? = MediaLibrary.can_control_playback?(socket.assigns.current_user, song)
|
||||
|
||||
cond do
|
||||
can_playback? and socket.assigns.active_song_id == id and MediaLibrary.playing?(song) ->
|
||||
can_playback? and active_song_id == song.id and MediaLibrary.playing?(song) ->
|
||||
MediaLibrary.pause_song(song)
|
||||
|
||||
receive do
|
||||
{MediaLibrary, %MediaLibrary.Events.Pause{song: song}} ->
|
||||
{:noreply, pause_song(socket, song)}
|
||||
end
|
||||
|
||||
can_playback? ->
|
||||
MediaLibrary.play_song(id)
|
||||
|
||||
true ->
|
||||
:noop
|
||||
end
|
||||
receive do
|
||||
{MediaLibrary, %MediaLibrary.Events.Play{song: song}} ->
|
||||
{:noreply, play_song(socket, song)}
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
true ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
|
@ -253,7 +310,10 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
end
|
||||
|
||||
def handle_info({MediaLibrary, %MediaLibrary.Events.NewPosition{song: song}}, socket) do
|
||||
{:noreply, stream_insert(socket, :songs, song, at: song.position)}
|
||||
{:noreply,
|
||||
socket
|
||||
|> stream_delete(:songs, song)
|
||||
|> stream_insert(:songs, song, at: song.position)}
|
||||
end
|
||||
|
||||
def handle_info({MediaLibrary, %MediaLibrary.Events.Play{song: song}}, socket) do
|
||||
|
@ -264,7 +324,10 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
{:noreply, pause_song(socket, song)}
|
||||
end
|
||||
|
||||
def handle_info({MediaLibrary, {%MediaLibrary.Song.TranscriptSegment{} = seg, song_id}}, socket) do
|
||||
def handle_info(
|
||||
{MediaLibrary, {%MediaLibrary.Song.Transcript.Segment{} = seg, song_id}},
|
||||
socket
|
||||
) do
|
||||
if socket.assigns.active_song_id == song_id do
|
||||
{:noreply, stream_insert(socket, :transcript_segments, seg)}
|
||||
else
|
||||
|
@ -348,36 +411,20 @@ defmodule LiveBeatsWeb.ProfileLive do
|
|||
end
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
if socket.assigns.owns_profile? do
|
||||
socket
|
||||
|> assign(:page_title, "Add Music")
|
||||
|> assign(:song, %MediaLibrary.Song{})
|
||||
|> show_upload_modal()
|
||||
else
|
||||
socket
|
||||
|> put_flash(:error, "You can't do that")
|
||||
|> redirect(to: profile_path(socket.assigns.current_user))
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_action(socket, :show, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Songs")
|
||||
|> assign(:song, nil)
|
||||
end
|
||||
|
||||
defp show_upload_modal(socket) do
|
||||
LayoutComponent.show_modal(UploadFormComponent, %{
|
||||
id: :new,
|
||||
confirm: {"Save", type: "submit", form: "song-form"},
|
||||
patch: profile_path(socket.assigns.current_user),
|
||||
song: socket.assigns.song,
|
||||
title: socket.assigns.page_title,
|
||||
current_user: socket.assigns.current_user
|
||||
})
|
||||
|
||||
socket
|
||||
defp apply_action(socket, :new, _params) do
|
||||
if socket.assigns.owns_profile? do
|
||||
assign(socket, :page_title, "Add Music")
|
||||
else
|
||||
socket
|
||||
|> put_flash(:error, "You can't do that")
|
||||
|> redirect(to: profile_path(socket.assigns.current_user))
|
||||
end
|
||||
end
|
||||
|
||||
defp assign_presences(socket) do
|
||||
|
|
|
@ -7,6 +7,10 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
|
|||
@max_songs 10
|
||||
|
||||
@impl true
|
||||
def update(%{action: :reset}, socket) do
|
||||
{:ok, reset_assigns(socket)}
|
||||
end
|
||||
|
||||
def update(%{action: {:duration, entry_ref, result}}, socket) do
|
||||
case result do
|
||||
{:ok, %MP3Stat{} = stat} ->
|
||||
|
@ -17,20 +21,37 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
|
|||
end
|
||||
end
|
||||
|
||||
def update(%{song: song} = assigns, socket) do
|
||||
def update(%{} = assigns, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(changesets: %{}, error_messages: [])
|
||||
|> allow_upload(:mp3,
|
||||
song_id: song.id,
|
||||
auto_upload: true,
|
||||
progress: &handle_progress/3,
|
||||
accept: ~w(.mp3),
|
||||
max_entries: @max_songs,
|
||||
max_file_size: 20_000_000,
|
||||
chunk_size: 64_000 * 3
|
||||
)}
|
||||
|> reset_assigns()}
|
||||
end
|
||||
|
||||
defp reset_assigns(socket) do
|
||||
socket =
|
||||
assign(socket,
|
||||
song: %MediaLibrary.Song{},
|
||||
changesets: %{},
|
||||
error_messages: []
|
||||
)
|
||||
|
||||
uploads = socket.assigns[:uploads][:mp3]
|
||||
|
||||
if uploads do
|
||||
Enum.reduce(uploads.entries, socket, fn entry, acc ->
|
||||
cancel_upload(acc, :mp3, entry.ref)
|
||||
end)
|
||||
else
|
||||
allow_upload(socket, :mp3,
|
||||
auto_upload: true,
|
||||
progress: &handle_progress/3,
|
||||
accept: ~w(.mp3),
|
||||
max_entries: @max_songs,
|
||||
max_file_size: 20_000_000,
|
||||
chunk_size: 64_000 * 3
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
@ -57,12 +78,15 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
|
|||
else
|
||||
case MediaLibrary.import_songs(current_user, changesets, &consume_entry(socket, &1, &2)) do
|
||||
{:ok, songs} ->
|
||||
send_update(socket.assigns.myself, %{action: :reset})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "#{map_size(songs)} song(s) uploaded")
|
||||
|> push_patch(to: profile_path(current_user))}
|
||||
|> push_js_cmd(socket.assigns.on_complete)}
|
||||
|
||||
{:error, {failed_op, reason}} ->
|
||||
IO.inspect({failed_op, reason})
|
||||
{:noreply, put_error(socket, {failed_op, reason})}
|
||||
end
|
||||
end
|
||||
|
@ -72,6 +96,10 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
|
|||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, reset_assigns(socket)}
|
||||
end
|
||||
|
||||
defp pending_stats?(socket) do
|
||||
Enum.find(socket.assigns.changesets, fn {_ref, chset} -> !chset.changes[:duration] end)
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div>
|
||||
<div id="upload-form">
|
||||
<p class="inline text-gray-500 text-sm">(songs expire every six hours)</p>
|
||||
|
||||
<.form
|
||||
|
@ -8,7 +8,8 @@
|
|||
class="space-y-8"
|
||||
phx-target={@myself}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
phx-auto-recover="ignore"
|
||||
phx-submit={JS.push("save", target: @myself, loading: "#song-form, #upload")}
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div class="space-y-2 sm:space-y-2">
|
||||
|
@ -62,7 +63,7 @@
|
|||
for="file-upload"
|
||||
class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
|
||||
>
|
||||
<span phx-click={js_exec("##{@uploads.mp3.ref}", "click", [])}>
|
||||
<span phx-click={js_call("##{@uploads.mp3.ref}", "click", [])}>
|
||||
Upload files
|
||||
</span>
|
||||
<.live_file_input upload={@uploads.mp3} class="sr-only" tabindex="0" />
|
||||
|
|
|
@ -11,10 +11,15 @@ defmodule LiveBeatsWeb.SettingsLive do
|
|||
|
||||
<div class="max-w-3xl px-4 mx-auto mt-6">
|
||||
<.form
|
||||
id="settings-form"
|
||||
:let={f}
|
||||
id="settings-form"
|
||||
for={@changeset}
|
||||
phx-change="validate"
|
||||
phx-change={
|
||||
JS.push("validate",
|
||||
loading:
|
||||
"#settings-form, #settings-form button, #settings-form input, #settings-form [phx-feedback-for]"
|
||||
)
|
||||
}
|
||||
phx-submit="save"
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
>
|
||||
|
@ -103,6 +108,8 @@ defmodule LiveBeatsWeb.SettingsLive do
|
|||
{:ok, assign(socket, changeset: changeset)}
|
||||
end
|
||||
|
||||
def handle_params(_, _, socket), do: {:noreply, socket}
|
||||
|
||||
def handle_event("validate", %{"user" => params}, socket) do
|
||||
changeset = Accounts.change_settings(socket.assigns.current_user, params)
|
||||
{:noreply, assign(socket, changeset: Map.put(changeset, :action, :validate))}
|
||||
|
|
13
mix.exs
13
mix.exs
|
@ -4,7 +4,7 @@ defmodule LiveBeats.MixProject do
|
|||
def project do
|
||||
[
|
||||
app: :live_beats,
|
||||
version: "0.1.0",
|
||||
version: "0.1.1",
|
||||
elixir: "~> 1.12",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
|
@ -33,10 +33,11 @@ defmodule LiveBeats.MixProject do
|
|||
defp deps do
|
||||
[
|
||||
{:phoenix, "~> 1.7.1"},
|
||||
{:phoenix_live_view, "~> 0.18.17"},
|
||||
{:phoenix_live_dashboard, "~> 0.7.2"},
|
||||
{:dns_cluster, ">= 0.0.0"},
|
||||
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view", override: true},
|
||||
{:phoenix_live_dashboard, "~> 0.8"},
|
||||
{:phoenix_ecto, "~> 4.4"},
|
||||
{:ecto_sql, "~> 3.6"},
|
||||
{:ecto_sql, "~> 3.11"},
|
||||
{:ecto_network, "~> 1.3.0"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:phoenix_html, "~> 3.3", override: true},
|
||||
|
@ -54,10 +55,10 @@ defmodule LiveBeats.MixProject do
|
|||
{:heroicons, "~> 0.2.2"},
|
||||
{:castore, "~> 0.1.13"},
|
||||
{:tailwind, "~> 0.2.0"},
|
||||
{:libcluster, "~> 3.3.1"},
|
||||
{:bumblebee, github: "elixir-nx/bumblebee"},
|
||||
{:exla, ">= 0.0.0"},
|
||||
{:req, "~> 0.3.7"}
|
||||
{:req, "~> 0.4"},
|
||||
{:flame, "~> 0.1.12"}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
67
mix.lock
67
mix.lock
|
@ -2,60 +2,75 @@
|
|||
"axon": {:hex, :axon, "0.5.1", "1ae3a2193df45e51fca912158320b2ca87cb7fba4df242bd3ebe245504d0ea1a", [:mix], [{:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:kino_vega_lite, "~> 0.1.7", [hex: :kino_vega_lite, repo: "hexpm", optional: true]}, {:nx, "~> 0.5.0", [hex: :nx, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "d36f2a11c34c6c2b458f54df5c71ffdb7ed91c6a9ccd908faba909c84cc6a38e"},
|
||||
"bumblebee": {:git, "https://github.com/elixir-nx/bumblebee.git", "fd9a8b1d149cdcebd5170e103b49e9f6f64ee482", []},
|
||||
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"complex": {:hex, :complex, "0.5.0", "af2d2331ff6170b61bb738695e481b27a66780e18763e066ee2cd863d0b1dd92", [:mix], [], "hexpm", "2683bd3c184466cfb94fad74cbfddfaa94b860e27ad4ca1bffe3bff169d91ef1"},
|
||||
"con_cache": {:hex, :con_cache, "1.1.0", "45c7c6cd6dc216e47636232e8c683734b7fe293221fccd9454fa1757bc685044", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8655f2ae13a1e56c8aef304d250814c7ed929c12810f126fc423ecc8e871593b"},
|
||||
"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.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
|
||||
"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"},
|
||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||
"db_connection": {:hex, :db_connection, "2.4.2", "f92e79aff2375299a16bcb069a14ee8615c3414863a6fef93156aee8e86c2ff3", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4fe53ca91b99f55ea249693a0229356a08f4d1a7931d8ffa79289b145fe83668"},
|
||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||
"ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"},
|
||||
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
|
||||
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
|
||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||
"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},
|
||||
"ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"},
|
||||
"ecto_network": {:hex, :ecto_network, "1.3.0", "1e77fa37c20e0f6a426d3862732f3317b0fa4c18f123d325f81752a491d7304e", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "053a5e46ef2837e8ea5ea97c82fa0f5494699209eddd764e663c85f11b2865bd"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.9.0", "2bb21210a2a13317e098a420a8c1cc58b0c3421ab8e3acfa96417dab7817918c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a8f3f720073b8b1ac4c978be25fa7960ed7fd44997420c304a4a2e200b596453"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.7.5", "784cc00f5fa24239067cc04d449437dcc5f59353c44eb08f188b2b146568738a", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "c3d63e8d5c92fa3880d89ecd41de59473fa2e83eeb68148155e25e8b95aa2887"},
|
||||
"erlexec": {:hex, :erlexec, "2.0.2", "995e40477de94c37ec1264cc3e52eb6273938e80c9bcc4f94110a3f1c0d9aba3", [:rebar3], [], "hexpm", "cc829a7c6c23d399832da2e998ea5ebc552232a6fe3eb1edb400178ec8287dcb"},
|
||||
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.2", "cee302b8e9ee198cc0d89f1de2a7d6a8921e1a556574476cf5590d2156590fe3", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "cc5bd945a22a99eece4721d734ae2452d3717e81c357a781c8574663254df4a1"},
|
||||
"exla": {:hex, :exla, "0.5.1", "8832aa299fe06ed9b772e004760b7c97e9d8dcbe40e9a4bfcbbe10b320b9c342", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nx, "~> 0.5.1", [hex: :nx, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xla, "~> 0.4.4", [hex: :xla, repo: "hexpm", optional: false]}], "hexpm", "48a990dbaf02bf5f288aa1360b5237c2f55db8bf52d4f63072f2b6a15d4e8375"},
|
||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||
"finch": {:hex, :finch, "0.13.0", "c881e5460ec563bf02d4f4584079e62201db676ed4c0ef3e59189331c4eddf7b", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49957dcde10dcdc042a123a507a9c5ec5a803f53646d451db2f7dea696fba6cc"},
|
||||
"floki": {:hex, :floki, "0.34.0", "002d0cc194b48794d74711731db004fafeb328fe676976f160685262d43706a8", [:mix], [], "hexpm", "9c3a9f43f40dde00332a589bd9d389b90c1f518aef500364d00636acc5ebc99c"},
|
||||
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
|
||||
"flame": {:hex, :flame, "0.1.12", "4d46b706d35d6eb22505d0e060fe41174052eaa38f778a4762fc74dd2c9df301", [:mix], [{:req, "~> 0.4.13", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "aa25de6614455ac01e33409c08db4560fa54dd837ad116aae16c8f7c011ccd76"},
|
||||
"floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
|
||||
"gettext": {:hex, :gettext, "0.20.0", "75ad71de05f2ef56991dbae224d35c68b098dd0e26918def5bb45591d5c8d429", [:mix], [], "hexpm", "1c03b177435e93a47441d7f681a7040bd2a816ece9e2666d1c9001035121eb3d"},
|
||||
"hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
|
||||
"heroicons": {:hex, :heroicons, "0.2.4", "12824795f25340415bffa9a501b96c89dc856ed732e82acccdab635fee92411b", [:mix], [{:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "7d6c51dc8ecaadb37943247da83ec2fbf3f6b479a23f4cf0195bd61ec2268128"},
|
||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
|
||||
"libcluster": {:hex, :libcluster, "3.3.1", "e7a4875cd1290cee7a693d6bd46076863e9e433708b01339783de6eff5b7f0aa", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b575ca63c1cd84e01f3fa0fc45e6eb945c1ee7ae8d441d33def999075e9e5398"},
|
||||
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
|
||||
"nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
"nx": {:hex, :nx, "0.5.1", "118134b8c97c2a8f86c87aa8434994c1cbbe139a306b89cca04e08dd46228067", [:mix], [{:complex, "~> 0.5", [hex: :complex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ceb8fbbe19b3c4252a7188d8b0e059fac9da0f4a4f3bb770fc665fdd0b29f0c5"},
|
||||
"nx_image": {:hex, :nx_image, "0.1.0", "ae10fa41fa95126f934d6160ef4320f7db583535fb868415f2562fe19969d245", [:mix], [{:nx, "~> 0.4", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "60a2928164cdca540b4c180ff25579b97a5f2a650fc890d40db3e1a7798c93ad"},
|
||||
"nx_signal": {:hex, :nx_signal, "0.1.0", "403ac73140e2f368e827e0aca1a3035abaf6d890b00376742b359a6838e00d7f", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "1c68f2f0d186700819287f37ee6154a11e06bf5dbb30b73fcc92776293309a05"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.1", "a029bde19d9c3b559e5c3d06c78b76e81396bedd456a6acedb42f9c7b2e535a9", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "ea9d4a85c3592e37efa07d0dc013254fda445885facaefddcbf646375c116457"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.0", "4fe222c0be55fdc3f9c711e24955fc42a7cd9b7a2f5f406f2580a567c335a573", [: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", "bebf0fc2d2113b61cb5968f585367234b7b4c21d963d691de7b4b2dc6cdaae6f"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.17", "74938b02f3c531bed3f87fe1ea39af6b5b2d26ab1405e77e76b8ef5df9ffa8a1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4b5710e19a29b8dc93b7af4bab4739c067a3cb759af01ffc3057165453dce38"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
|
||||
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [: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", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
||||
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
||||
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "2785f5bb2df6eb2863567e3c9da622b4499968fc", []},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
|
||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
|
||||
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"req": {:hex, :req, "0.3.7", "e4ea5d73e3f434c0a15601bb85330ffd0e57860c098283e98c28d21172a1f749", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a7d3c0bec7d2d23198ef12676d2c950bec258308c6a5123eb98465030205f39c"},
|
||||
"req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"},
|
||||
"rustler_precompiled": {:hex, :rustler_precompiled, "0.6.1", "160b545bce8bf9a3f1b436b2c10f53574036a0db628e40f393328cbbe593602f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "0dd269fa261c4e3df290b12031c575fff07a542749f7b0e8b744d72d66c43600"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
|
||||
"swoosh": {:hex, :swoosh, "1.8.2", "af9a22ab2c0d20b266f61acca737fa11a121902de9466a39e91bacdce012101c", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d058ba750eafadb6c09a84a352c14c5d1eeeda6e84945fcc95785b7f3067b7db"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.0", "95f9e4a32020c5bec480f1d6a43a49ac8030b13183127b577605f506d6e13a66", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "385e939fcd7fe4654be5130b187e358aaabade385513f9d200ffecdbb9552a9e"},
|
||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
|
||||
"tokenizers": {:hex, :tokenizers, "0.3.0", "1aebe61c68cf36e3ea4423a357b196e226c18a8b3206afe59d257f038833deb4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "6c6de26911fd875fc1aead92ee40efb3e4271f54a312cc52f073012f2a134201"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"unpickler": {:hex, :unpickler, "0.1.0", "c2262c0819e6985b761e7107546cef96a485f401816be5304a65fdd200d5bd6a", [:mix], [], "hexpm", "e2b3f61e62406187ac52afead8a63bfb4e49394028993f3c4c42712743cab79e"},
|
||||
"unzip": {:hex, :unzip, "0.8.0", "ee21d87c21b01567317387dab4228ac570ca15b41cfc221a067354cbf8e68c4d", [:mix], [], "hexpm", "ffa67a483efcedcb5876971a50947222e104d5f8fea2c4a0441e6f7967854827"},
|
||||
"websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.4.5", "30038a3715067f51a9580562c05a3a8d501126030336ffc6edb53bf57d6d2d26", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.4", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "1d9812dc7e703c205049426fd4fe0852a247a825f91b099e53dc96f68bafe4c8"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
|
||||
"xla": {:hex, :xla, "0.4.4", "c3a8ed1f579bda949df505e49ff65415c8281d991fbd6ae1d8f3c5d0fd155f54", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "484f3f9011db3c9f1ff1e98eecefd382f3882a07ada540fd58803db1d2dab671"},
|
||||
}
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
defmodule LiveBeats.Repo.Migrations.CreateUserAuth do
|
||||
use Ecto.Migration
|
||||
import Ecto.Query
|
||||
|
||||
def change do
|
||||
execute "CREATE EXTENSION IF NOT EXISTS citext", ""
|
||||
# email_type =
|
||||
# if repo().exists?(
|
||||
# from(e in "pg_available_extensions", where: e.name == "citext", select: e.name)
|
||||
# ) do
|
||||
# execute "CREATE EXTENSION IF NOT EXISTS citext", ""
|
||||
# :citext
|
||||
# else
|
||||
# :string
|
||||
# end
|
||||
|
||||
create table(:users) do
|
||||
add :email, :citext, null: false
|
||||
add :email, :string, null: false
|
||||
add :username, :string, null: false
|
||||
add :name, :string
|
||||
add :role, :string, null: false
|
||||
|
@ -16,8 +25,8 @@ defmodule LiveBeats.Repo.Migrations.CreateUserAuth do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
create unique_index(:users, [:email])
|
||||
create unique_index(:users, [:username])
|
||||
create unique_index(:users, ["lower(email)"], name: :users_email_index)
|
||||
create unique_index(:users, ["lower(username)"], name: :users_username_index)
|
||||
|
||||
create table(:identities) do
|
||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule LiveBeats.Repo.Migrations.AddServerIpToSongs do
|
|||
|
||||
def change do
|
||||
alter table(:songs) do
|
||||
add :server_ip, :inet
|
||||
add :server_ip, :string
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,11 +5,6 @@ defmodule LiveBeats.Repo.Migrations.AddSongsNumberToUsers do
|
|||
alter table(:users) do
|
||||
add :songs_count, :integer, null: false, default: 0
|
||||
end
|
||||
|
||||
execute("
|
||||
UPDATE users set songs_count =
|
||||
(SELECT count (*) from songs
|
||||
where songs.user_id = users.id)")
|
||||
end
|
||||
|
||||
def down do
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule LiveBeats.Repo.Migrations.AddTranscriptsToSongs do
|
|||
|
||||
def change do
|
||||
alter table(:songs) do
|
||||
add :transcript_segments, {:array, :map}, null: false, default: []
|
||||
add :transcript, :jsonb, null: false, default: "{\"segments\": []}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
ip=$(grep fly-local-6pn /etc/hosts | cut -f 1)
|
||||
export RELEASE_DISTRIBUTION=name
|
||||
export RELEASE_NODE=$FLY_APP_NAME@$ip
|
||||
export LIVE_BEATS_SERVER_IP=$ip
|
||||
export ERL_AFLAGS="-proto_dist inet6_tcp"
|
||||
export ECTO_IPV6="true"
|
||||
export DNS_CLUSTER_QUERY="${FLY_APP_NAME}.internal"
|
||||
export RELEASE_DISTRIBUTION="name"
|
||||
export RELEASE_NODE="${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}"
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
#!/bin/sh
|
||||
if [ -n "${BUCKET_MOUNT}" ]; then
|
||||
mkdir -p "${BUCKET_MOUNT}"
|
||||
chown nobody:nogroup "${BUCKET_MOUNT}"
|
||||
echo "Mounting S3 bucket at ${BUCKET_MOUNT}"
|
||||
echo $AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY > ${HOME}/.passwd-s3fs
|
||||
chmod 600 ${HOME}/.passwd-s3fs
|
||||
s3fs livebeats-store "${BUCKET_MOUNT}" \
|
||||
-o url="${AWS_ENDPOINT_URL_S3}" \
|
||||
-o dbglevel=info \
|
||||
-o curldbg \
|
||||
-o use_path_request_style \
|
||||
-o allow_other \
|
||||
-o _netdev \
|
||||
-o uid="$(id -u nobody)" \
|
||||
-o gid="$(id -g nobody)"
|
||||
fi
|
||||
cd -P -- "$(dirname -- "$0")"
|
||||
PHX_SERVER=true exec ./live_beats start
|
||||
su -s /bin/bash nobody -c 'PHX_SERVER=true exec ./live_beats start'
|
||||
|
||||
|
|
Loading…
Reference in a new issue