ffmpeg hls playlist implementation
This commit is contained in:
parent
2e4887a5a1
commit
8aa8f231ed
|
@ -8,7 +8,10 @@
|
|||
"python310Packages.pip@latest",
|
||||
"hcloud@latest",
|
||||
"lazydocker@latest",
|
||||
"ruby@latest"
|
||||
"ruby@latest",
|
||||
"chisel@latest",
|
||||
"bento4@latest",
|
||||
"shaka-packager@latest"
|
||||
],
|
||||
"env": {
|
||||
"DEVBOX_COREPACK_ENABLED": "true",
|
||||
|
@ -26,6 +29,7 @@
|
|||
"test": [
|
||||
"echo \"Error: no test specified\" && exit 1"
|
||||
],
|
||||
"tunnel": "dotenvx run -f ./.kamal/secrets.development -- chisel client bright.fp.sbtp.xyz:9090 R:4000",
|
||||
"backup": "docker exec -t postgres_db pg_dumpall -c -U postgres > ./backups/dev_`date +%Y-%m-%d_%H_%M_%S`.sql"
|
||||
}
|
||||
}
|
||||
|
|
144
devbox.lock
144
devbox.lock
|
@ -1,6 +1,102 @@
|
|||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"bento4@latest": {
|
||||
"last_modified": "2025-01-25T23:17:58Z",
|
||||
"resolved": "github:NixOS/nixpkgs/b582bb5b0d7af253b05d58314b85ab8ec46b8d19#bento4",
|
||||
"source": "devbox-search",
|
||||
"version": "1.6.0-641",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/c88fmklr5716ksfd30103l5ga96jqydc-bento4-1.6.0-641",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/c88fmklr5716ksfd30103l5ga96jqydc-bento4-1.6.0-641"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/dzv9rzqawf9nd529lx0sb6zk6k30bllq-bento4-1.6.0-641",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/dzv9rzqawf9nd529lx0sb6zk6k30bllq-bento4-1.6.0-641"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/3w09k9fp9d76a3vh6zmifbssv83ngv5q-bento4-1.6.0-641",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/3w09k9fp9d76a3vh6zmifbssv83ngv5q-bento4-1.6.0-641"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/nnflb6279al7r7ad0qrraln3w5brpba0-bento4-1.6.0-641",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/nnflb6279al7r7ad0qrraln3w5brpba0-bento4-1.6.0-641"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chisel@latest": {
|
||||
"last_modified": "2024-12-23T21:10:33Z",
|
||||
"resolved": "github:NixOS/nixpkgs/de1864217bfa9b5845f465e771e0ecb48b30e02d#chisel",
|
||||
"source": "devbox-search",
|
||||
"version": "1.10.1",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/2rls3b9lq2i3g53zpr09d6ph43mgfxwz-chisel-1.10.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/2rls3b9lq2i3g53zpr09d6ph43mgfxwz-chisel-1.10.1"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/7ff1z4mr0ia2ifdgggpqkbc2j795ccy4-chisel-1.10.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/7ff1z4mr0ia2ifdgggpqkbc2j795ccy4-chisel-1.10.1"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/2cb5sa449vpah2g4q4prvqfz1dcf1rdw-chisel-1.10.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/2cb5sa449vpah2g4q4prvqfz1dcf1rdw-chisel-1.10.1"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/pphc5mnhx6mb08ak6mb3rnh061427xbj-chisel-1.10.1",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/pphc5mnhx6mb08ak6mb3rnh061427xbj-chisel-1.10.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ffmpeg@latest": {
|
||||
"last_modified": "2025-01-07T09:15:50Z",
|
||||
"resolved": "github:NixOS/nixpkgs/8c9fd3e564728e90829ee7dbac6edc972971cd0f#ffmpeg",
|
||||
|
@ -517,6 +613,54 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"shaka-packager@latest": {
|
||||
"last_modified": "2025-01-25T23:17:58Z",
|
||||
"resolved": "github:NixOS/nixpkgs/b582bb5b0d7af253b05d58314b85ab8ec46b8d19#shaka-packager",
|
||||
"source": "devbox-search",
|
||||
"version": "3.4.2",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/l0srzffgawm37rnii66r3vbxhh699f7w-shaka-packager-3.4.2",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/l0srzffgawm37rnii66r3vbxhh699f7w-shaka-packager-3.4.2"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/40bpzld1ccq4kwjfrrncdj9xqpmrk537-shaka-packager-3.4.2",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/40bpzld1ccq4kwjfrrncdj9xqpmrk537-shaka-packager-3.4.2"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/npgp484fhvi2xpfi1f7bpcnxc7a0krq2-shaka-packager-3.4.2",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/npgp484fhvi2xpfi1f7bpcnxc7a0krq2-shaka-packager-3.4.2"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"outputs": [
|
||||
{
|
||||
"name": "out",
|
||||
"path": "/nix/store/z0k69qksnh4sk4fagmmv7pcwy0sv7kby-shaka-packager-3.4.2",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"store_path": "/nix/store/z0k69qksnh4sk4fagmmv7pcwy0sv7kby-shaka-packager-3.4.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"yt-dlp@latest": {
|
||||
"last_modified": "2025-01-03T14:51:55Z",
|
||||
"resolved": "github:NixOS/nixpkgs/a27871180d30ebee8aa6b11bf7fef8a52f024733#yt-dlp",
|
||||
|
|
|
@ -1,58 +1,12 @@
|
|||
services:
|
||||
|
||||
# This service is just here for env var re-use between all the superstreamer-* services.
|
||||
# IDK if there is a way to do this without an image so we just run alpine which quits right away.
|
||||
superstreamer:
|
||||
image: alpine
|
||||
environment:
|
||||
- PUBLIC_API_ENDPOINT=http://localhost:52001
|
||||
- PUBLIC_STITCHER_ENDPOINT=http://localhost:52002
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- DATABASE_URI=postgres://postgres:password@db:5432/superstreamer
|
||||
env_file: .kamal/secrets.development
|
||||
|
||||
superstreamer-app:
|
||||
extends: superstreamer
|
||||
image: "superstreamerapp/app:alpha"
|
||||
opentracker:
|
||||
image: anthonyzou/opentracker:latest
|
||||
ports:
|
||||
- 52000:52000
|
||||
|
||||
|
||||
superstreamer-api:
|
||||
extends: superstreamer
|
||||
image: "superstreamerapp/api:alpha"
|
||||
restart: always
|
||||
ports:
|
||||
- 52001:52001
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
superstreamer-stitcher:
|
||||
extends: superstreamer
|
||||
image: "superstreamerapp/stitcher:alpha"
|
||||
restart: always
|
||||
ports:
|
||||
- 52002:52002
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
superstreamer-artisan:
|
||||
extends: superstreamer
|
||||
image: "superstreamerapp/artisan:alpha"
|
||||
restart: always
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
redis:
|
||||
image: redis/redis-stack-server:7.2.0-v6
|
||||
ports:
|
||||
- 127.0.0.1:6379:6379
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
|
||||
- "6969:6969/tcp"
|
||||
- "6969:6969/udp"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- ./packages/opentracker/opentracker.conf:/etc/opentracker.conf:ro
|
||||
|
||||
bright:
|
||||
container_name: bright
|
||||
|
|
|
@ -22,7 +22,7 @@ ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
|
|||
FROM ${BUILDER_IMAGE} AS builder
|
||||
|
||||
# install build dependencies
|
||||
RUN apt-get update -y && apt-get install -y build-essential git inotify-tools \
|
||||
RUN apt-get update -y && apt-get install -y build-essential git inotify-tools ffmpeg \
|
||||
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
|
||||
|
||||
# prepare build dir
|
||||
|
@ -43,6 +43,7 @@ RUN mix deps.get --only $MIX_ENV
|
|||
RUN mkdir config
|
||||
RUN mkdir contrib
|
||||
|
||||
|
||||
# copy compile-time config files before we compile dependencies
|
||||
# to ensure any relevant config change will trigger the dependencies
|
||||
# to be re-compiled.
|
||||
|
@ -79,6 +80,7 @@ RUN mix release
|
|||
FROM builder AS dev
|
||||
COPY ./services/bright/config/test.exs config/test.exs
|
||||
RUN ls -la ./contrib/
|
||||
RUN mkdir -p ~/.cache/futureporn
|
||||
CMD [ "mix", "phx.server" ]
|
||||
|
||||
|
||||
|
@ -115,4 +117,5 @@ USER nobody
|
|||
# above and adding an entrypoint. See https://github.com/krallin/tini for details
|
||||
# ENTRYPOINT ["/tini", "--"]
|
||||
|
||||
RUN mkdir -p ~/.config/futureporn
|
||||
CMD ["/app/bin/server"]
|
||||
|
|
|
@ -27,6 +27,7 @@ config :bright, BrightWeb.Endpoint,
|
|||
|
||||
config :bright, Oban,
|
||||
engine: Oban.Engines.Basic,
|
||||
notifier: Oban.Notifiers.PG,
|
||||
queues: [default: 10],
|
||||
repo: Bright.Repo,
|
||||
plugins: [
|
||||
|
|
|
@ -31,6 +31,8 @@ config :bright,
|
|||
public_s3_endpoint: System.get_env("PUBLIC_S3_ENDPOINT"),
|
||||
s3_cdn_endpoint: System.get_env("PUBLIC_S3_ENDPOINT")
|
||||
|
||||
config :bright, :buckets,
|
||||
media: System.get_env("AWS_BUCKET")
|
||||
|
||||
# @see https://elixirforum.com/t/backblaze-and-ex-aws-ex-aws-s3-2-4-3-presign-url-issue/56805
|
||||
config :ex_aws,
|
||||
|
|
|
@ -6,4 +6,18 @@ defmodule Bright do
|
|||
Contexts are also responsible for managing your data, regardless
|
||||
if it comes from the database, an external API or others.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Looks up `Application` config or raises if keyspace is not configured.
|
||||
"""
|
||||
def config([main_key | rest] = keyspace) when is_list(keyspace) do
|
||||
main = Application.fetch_env!(:bright, main_key)
|
||||
|
||||
Enum.reduce(rest, main, fn next_key, current ->
|
||||
case Keyword.fetch(current, next_key) do
|
||||
{:ok, val} -> val
|
||||
:error -> raise ArgumentError, "no config found under #{inspect(keyspace)}"
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
defmodule Bright.Blog do
|
||||
@moduledoc """
|
||||
The Blog context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Bright.Repo
|
||||
|
||||
alias Bright.Blog.Post
|
||||
|
||||
@doc """
|
||||
Returns the list of posts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_posts()
|
||||
[%Post{}, ...]
|
||||
|
||||
"""
|
||||
def list_posts do
|
||||
Repo.all(Post)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single post.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Post does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_post!(123)
|
||||
%Post{}
|
||||
|
||||
iex> get_post!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_post!(id), do: Repo.get!(Post, id)
|
||||
|
||||
@doc """
|
||||
Creates a post.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_post(%{field: value})
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> create_post(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_post(attrs \\ %{}) do
|
||||
%Post{}
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a post.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_post(post, %{field: new_value})
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> update_post(post, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_post(%Post{} = post, attrs) do
|
||||
post
|
||||
|> Post.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a post.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_post(post)
|
||||
{:ok, %Post{}}
|
||||
|
||||
iex> delete_post(post)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_post(%Post{} = post) do
|
||||
Repo.delete(post)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking post changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_post(post)
|
||||
%Ecto.Changeset{data: %Post{}}
|
||||
|
||||
"""
|
||||
def change_post(%Post{} = post, attrs \\ %{}) do
|
||||
Post.changeset(post, attrs)
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
defmodule Bright.Blog.Post do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "posts" do
|
||||
field :title, :string
|
||||
field :body, :string
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(post, attrs) do
|
||||
post
|
||||
|> cast(attrs, [:title, :body])
|
||||
|> validate_required([:title, :body])
|
||||
end
|
||||
end
|
|
@ -10,6 +10,10 @@ defmodule Bright.Cache do
|
|||
|
||||
require Logger
|
||||
|
||||
def cache_dir do
|
||||
@cache_dir
|
||||
end
|
||||
|
||||
def generate_basename(input) do
|
||||
prefix = :crypto.strong_rand_bytes(6) |> Base.encode64(padding: false) |> String.replace(~r/[^a-zA-Z0-9]/, "")
|
||||
base = Path.basename(input)
|
||||
|
|
|
@ -1,214 +0,0 @@
|
|||
defmodule Bright.Catalog do
|
||||
@moduledoc """
|
||||
The Catalog context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Bright.Repo
|
||||
|
||||
alias Bright.Catalog.Product
|
||||
alias Bright.Catalog.Category
|
||||
|
||||
@doc """
|
||||
Returns the list of products.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_products()
|
||||
[%Product{}, ...]
|
||||
|
||||
"""
|
||||
def list_products do
|
||||
Repo.all(Product)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single product.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Product does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_product!(123)
|
||||
%Product{}
|
||||
|
||||
iex> get_product!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_product!(id) do
|
||||
Product
|
||||
|> Repo.get!(id)
|
||||
|> Repo.preload(:categories)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a product.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_product(%{field: value})
|
||||
{:ok, %Product{}}
|
||||
|
||||
iex> create_product(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_product(attrs \\ %{}) do
|
||||
%Product{}
|
||||
|> change_product(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a product.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_product(product, %{field: new_value})
|
||||
{:ok, %Product{}}
|
||||
|
||||
iex> update_product(product, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_product(%Product{} = product, attrs) do
|
||||
product
|
||||
|> change_product(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a product.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_product(product)
|
||||
{:ok, %Product{}}
|
||||
|
||||
iex> delete_product(product)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_product(%Product{} = product) do
|
||||
Repo.delete(product)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking product changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_product(product)
|
||||
%Ecto.Changeset{data: %Product{}}
|
||||
|
||||
"""
|
||||
def change_product(%Product{} = product, attrs \\ %{}) do
|
||||
categories = list_categories_by_id(attrs["category_ids"])
|
||||
product
|
||||
|> Repo.preload(:categories)
|
||||
|> Product.changeset(attrs)
|
||||
|> Ecto.Changeset.put_assoc(:categories, categories)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns the list of categories.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_categories()
|
||||
[%Category{}, ...]
|
||||
|
||||
"""
|
||||
def list_categories do
|
||||
Repo.all(Category)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Gets a single category.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Category does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_category!(123)
|
||||
%Category{}
|
||||
|
||||
iex> get_category!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_category!(id), do: Repo.get!(Category, id)
|
||||
|
||||
@doc """
|
||||
Creates a category.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_category(%{field: value})
|
||||
{:ok, %Category{}}
|
||||
|
||||
iex> create_category(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_category(attrs \\ %{}) do
|
||||
%Category{}
|
||||
|> Category.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a category.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_category(category, %{field: new_value})
|
||||
{:ok, %Category{}}
|
||||
|
||||
iex> update_category(category, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_category(%Category{} = category, attrs) do
|
||||
category
|
||||
|> Category.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a category.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_category(category)
|
||||
{:ok, %Category{}}
|
||||
|
||||
iex> delete_category(category)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_category(%Category{} = category) do
|
||||
Repo.delete(category)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking category changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_category(category)
|
||||
%Ecto.Changeset{data: %Category{}}
|
||||
|
||||
"""
|
||||
def change_category(%Category{} = category, attrs \\ %{}) do
|
||||
Category.changeset(category, attrs)
|
||||
end
|
||||
|
||||
def list_categories_by_id(nil), do: []
|
||||
def list_categories_by_id(category_ids) do
|
||||
Repo.all(from c in Category, where: c.id in ^category_ids)
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
defmodule Bright.Catalog.Category do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "categories" do
|
||||
field :title, :string
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(category, attrs) do
|
||||
category
|
||||
|> cast(attrs, [:title])
|
||||
|> validate_required([:title])
|
||||
|> unique_constraint(:title)
|
||||
end
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
defmodule Bright.Catalog.Product do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Bright.Catalog.Category
|
||||
|
||||
schema "products" do
|
||||
field :description, :string
|
||||
field :title, :string
|
||||
field :price, :decimal
|
||||
field :views, :integer
|
||||
|
||||
many_to_many :categories, Category, join_through: "product_categories", on_replace: :delete
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(product, attrs) do
|
||||
product
|
||||
|> cast(attrs, [:title, :description, :price, :views])
|
||||
|> validate_required([:title, :description, :price])
|
||||
end
|
||||
end
|
|
@ -5,8 +5,8 @@ defmodule Bright.Downloader do
|
|||
|
||||
def get(url) do
|
||||
filename = Bright.Cache.generate_filename(url)
|
||||
IO.puts("Downloader getting url=#{url}")
|
||||
|
||||
IO.puts("Downloader downloading to filename=#{filename}")
|
||||
|
||||
try do
|
||||
{download!(url, filename), filename}
|
||||
|
@ -17,7 +17,8 @@ defmodule Bright.Downloader do
|
|||
end
|
||||
|
||||
# greets https://elixirforum.com/t/how-to-download-big-files/9173/4
|
||||
defp download!(file_url, filename) do
|
||||
def download!(file_url, filename) do
|
||||
IO.puts("Downloader downloading file_url=#{file_url} to filename=#{filename}")
|
||||
file =
|
||||
if File.exists?(filename) do
|
||||
File.open!(filename, [:append])
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
defmodule Bright.Events do
|
||||
|
||||
|
||||
defmodule ThumbnailsGenerated do
|
||||
defstruct vod: nil
|
||||
end
|
||||
|
||||
defmodule ProcessingQueued do
|
||||
defstruct vod: nil
|
||||
end
|
||||
|
||||
defmodule ProcessingProgressed do
|
||||
defstruct vod: nil, stage: nil, pct: nil
|
||||
end
|
||||
|
||||
defmodule ProcessingCompleted do
|
||||
defstruct vod: nil, action: nil, url: nil
|
||||
end
|
||||
|
||||
defmodule ProcessingFailed do
|
||||
defstruct vod: nil, attempt: nil, max_attempts: nil
|
||||
end
|
||||
end
|
|
@ -30,7 +30,54 @@ defmodule Bright.Images do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
get the number of frames in a video.
|
||||
this is a fallback if get_video_framecount/1 fails
|
||||
|
||||
This code is copied from ffmpex, making a slight change to cmd_args because we need to set `-count_frames`.
|
||||
in ffmpex we aren't able to set cmd_args ourselves
|
||||
"""
|
||||
def get_video_framecount_slow(file_path) do
|
||||
|
||||
cmd_args = ["-print_format", "json", "-show_streams", "-count_frames", file_path]
|
||||
|
||||
{:ok, streams} = case Rambo.run(ffprobe_path(), cmd_args, log: false) do
|
||||
{:ok, %{out: result}} ->
|
||||
streams =
|
||||
result
|
||||
|> Jason.decode!()
|
||||
|> Map.get("streams", [])
|
||||
|
||||
{:ok, streams}
|
||||
|
||||
{:error, %{err: result}} ->
|
||||
file_error(file_path, result)
|
||||
end
|
||||
|
||||
streams
|
||||
|> Enum.find(fn stream -> stream["codec_type"] == "video" end)
|
||||
|> case do
|
||||
nil -> {:error, "No video stream found"}
|
||||
video_stream ->
|
||||
|
||||
nb_read_frames =
|
||||
video_stream
|
||||
|> Map.get("nb_read_frames", %{})
|
||||
|
||||
case nb_read_frames do
|
||||
nil -> {:error, "nb_read_frames not found. (nil)"}
|
||||
%{} -> {:error, "nb_read_frames not found. (empty map.)"}
|
||||
nb_read_frames ->
|
||||
case Integer.parse(nb_read_frames) do
|
||||
{number, _} -> {:ok, number}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def get_video_framecount(file_path) do
|
||||
IO.puts "get_video_framecount using file_path=#{file_path}"
|
||||
case FFprobe.streams(file_path) do
|
||||
{:ok, streams} ->
|
||||
streams
|
||||
|
@ -43,8 +90,8 @@ defmodule Bright.Images do
|
|||
|> Map.get("nb_frames", %{})
|
||||
|
||||
case nb_frames do
|
||||
nil -> {:error, "nb_frames not found"}
|
||||
%{} -> {:error, "nb_frames not found. (empty map)"}
|
||||
nil -> {:error, "nb_frames not found. (nil)"}
|
||||
%{} -> get_video_framecount_slow(file_path)
|
||||
nb_frames ->
|
||||
case Integer.parse(nb_frames) do
|
||||
{number, _} -> {:ok, number}
|
||||
|
@ -56,6 +103,8 @@ defmodule Bright.Images do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
defp gen_thumb(input_file, output_file) do
|
||||
case get_video_framecount(input_file) do
|
||||
{:error, reason} -> {:error, reason}
|
||||
|
@ -100,7 +149,33 @@ defmodule Bright.Images do
|
|||
gen_thumb(input_file, output_file)
|
||||
end
|
||||
|
||||
## copied from ffmpex
|
||||
defp file_error(file_path, error_text) do
|
||||
cond do
|
||||
File.exists?(file_path) -> {:error, :invalid_file}
|
||||
String.contains?(error_text, "Invalid data found when processing input") -> {:error, :invalid_file}
|
||||
String.contains?(error_text, "404 Not Found") -> {:error, :no_such_file}
|
||||
true -> {:error, :no_such_file}
|
||||
end
|
||||
end
|
||||
|
||||
# Read ffprobe path from config. If unspecified, check if `ffprobe` is in env $PATH.
|
||||
# If it is not, then raise a error.
|
||||
defp ffprobe_path do
|
||||
case Application.get_env(:ffmpex, :ffprobe_path, nil) do
|
||||
nil ->
|
||||
case System.find_executable("ffprobe") do
|
||||
nil ->
|
||||
raise "FFmpeg not installed"
|
||||
|
||||
path ->
|
||||
path
|
||||
end
|
||||
|
||||
path ->
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
defmodule Bright.Mailer do
|
||||
use Swoosh.Mailer, otp_app: :bright
|
||||
end
|
|
@ -1,239 +1,70 @@
|
|||
|
||||
|
||||
|
||||
defmodule Bright.ObanWorkers.CreateHlsPlaylist do
|
||||
use Oban.Worker, queue: :default, max_attempts: 6
|
||||
use Oban.Worker, queue: :default, max_attempts: 3
|
||||
|
||||
alias Bright.Repo
|
||||
alias Bright.Streams
|
||||
alias Bright.Streams.Vod
|
||||
|
||||
alias Bright.{
|
||||
Repo,
|
||||
Downloader,
|
||||
B2,
|
||||
Images,
|
||||
Cache
|
||||
}
|
||||
require Logger
|
||||
|
||||
@auth_token Application.get_env(:bright, :superstreamer_auth_token)
|
||||
@superstreamer_url System.get_env("SUPERSTREAMER_URL")
|
||||
@public_s3_endpoint Application.get_env(:bright, :s3_cdn_endpoint)
|
||||
|
||||
|
||||
# args: %{"vod_id" => 10, "input_url" => "http://38.242.193.246:8081/fixtures/2024-12-19T03-10-30Z.ts"}
|
||||
import Ecto.Query, warn: false
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{args: %{"vod_id" => vod_id}}) do
|
||||
Logger.info(">>>> create_hls_playlist is performing. vod_id=#{vod_id}")
|
||||
vod = Repo.get!(Vod, vod_id)
|
||||
def perform(%Oban.Job{args: %{"vod_id" => vod_id}} = job) do
|
||||
vod = Streams.get_vod!(vod_id)
|
||||
build_transmuxer(job, vod)
|
||||
|
||||
payload = build_payload(vod.origin_temp_input_url)
|
||||
|
||||
Logger.info("Starting transcoding for VOD ID #{vod_id}")
|
||||
|
||||
with {:ok, transcode_job_id} <- start_transcode(payload),
|
||||
{:ok, asset_id} <- poll_job_completion(transcode_job_id),
|
||||
{:ok, package_job_id} <- start_package(asset_id),
|
||||
{:ok, asset_id} <- poll_job_completion(package_job_id) do
|
||||
update_vod_with_playlist_url(vod, asset_id)
|
||||
|
||||
Logger.info("HLS playlist created and updated for VOD ID #{vod_id}")
|
||||
else
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to create HLS playlist for VOD ID #{vod_id}: #{inspect(reason)}")
|
||||
{:error, reason}
|
||||
end
|
||||
# IDK how to use liveview, pubsub, etc. so I disabled this.
|
||||
# ** (ArgumentError) unknown registry: nil. Either the registry name is invalid or the registry is not running, possibly because its application isn't started
|
||||
# (elixir 1.17.3) lib/registry.ex:1086: Registry.meta/2
|
||||
# (phoenix_pubsub 2.1.3) lib/phoenix/pubsub.ex:148: Phoenix.PubSub.broadcast/4
|
||||
# (phoenix_pubsub 2.1.3) lib/phoenix/pubsub.ex:241: Phoenix.PubSub.broadcast!/4
|
||||
# (bright 0.1.0) lib/bright/oban_workers/create_hls_playlist.ex:45: Bright.ObanWorkers.CreateHlsPlaylist.await_transmuxer/3
|
||||
# (oban 2.19.0) lib/oban/queue/executor.ex:145: Oban.Queue.Executor.perform/1
|
||||
# (oban 2.19.0) lib/oban/queue/executor.ex:77: Oban.Queue.Executor.call/1
|
||||
# (elixir 1.17.3) lib/task/supervised.ex:101: Task.Supervised.invoke_mfa/2
|
||||
# (elixir 1.17.3) lib/task/supervised.ex:36: Task.Supervised.reply/4
|
||||
await_transmuxer(vod)
|
||||
end
|
||||
|
||||
defp build_payload(input_url) do
|
||||
%{
|
||||
"inputs" => [
|
||||
%{"type" => "audio", "path" => input_url, "language" => "eng"},
|
||||
%{"type" => "video", "path" => input_url}
|
||||
],
|
||||
"streams" => [
|
||||
%{"type" => "video", "codec" => "h264", "height" => 1080},
|
||||
# %{"type" => "video", "codec" => "h264", "height" => 720}, # when I enabled this, I see a superstreamer error? -- "header 'content-length' is listed in signed headers, but is not present "
|
||||
%{"type" => "video", "codec" => "h264", "height" => 144},
|
||||
%{"type" => "audio", "codec" => "aac"}
|
||||
],
|
||||
"tag" => "create_hls_playlist"
|
||||
}
|
||||
end
|
||||
defp build_transmuxer(job, %Vod{} = vod) do
|
||||
job_pid = self()
|
||||
|
||||
Task.async(fn ->
|
||||
try do
|
||||
hls_video =
|
||||
Streams.transmux_to_hls(vod, fn progress ->
|
||||
send(job_pid, {:progress, progress})
|
||||
end)
|
||||
|
||||
defp start_transcode(payload) do
|
||||
Logger.info("Starting transcode with payload: #{inspect(payload)}")
|
||||
IO.puts "Starting transcode with payload: #{inspect(payload)}"
|
||||
|
||||
headers = auth_headers()
|
||||
|
||||
Logger.info("auth headers as follows")
|
||||
Logger.info(inspect(headers))
|
||||
Logger.info("@superstreamer_url=#{@superstreamer_url}")
|
||||
|
||||
|
||||
if is_nil(@superstreamer_url) do
|
||||
Logger.error("The @superstreamer_url is nil. This must be set before proceeding.")
|
||||
raise "The @superstreamer_url is not configured."
|
||||
end
|
||||
Logger.info("now we will POST /transcode to superstreamer_url=#{@superstreamer_url}")
|
||||
data = case HTTPoison.post("#{@superstreamer_url}/transcode", Jason.encode!(payload), headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
[] ->
|
||||
{:error, "We got an empty response from Superstreamer"}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to POST /transcode: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
Logger.info("we got some data as follows. #{inspect(data)}")
|
||||
|
||||
formatted = case data do
|
||||
{:ok, %{"jobId" => transcode_job_id}} ->
|
||||
{:ok, transcode_job_id}
|
||||
end
|
||||
|
||||
Logger.info("start_transcode has finished it's duties and is returning the following formatted data.")
|
||||
Logger.info(inspect(formatted))
|
||||
|
||||
formatted
|
||||
|
||||
end
|
||||
|
||||
defp start_package(asset_id) do
|
||||
payload = %{
|
||||
"assetId" => asset_id,
|
||||
"concurrency" => 1,
|
||||
"public" => false
|
||||
}
|
||||
|
||||
Logger.info("Starting packaging for asset ID #{asset_id}")
|
||||
|
||||
headers = auth_headers()
|
||||
|
||||
Logger.info("auth headers as follows")
|
||||
Logger.info(inspect(headers))
|
||||
|
||||
Logger.info("now we will POST /package to superstreamer_url=#{@superstreamer_url}")
|
||||
data = case HTTPoison.post("#{@superstreamer_url}/package", Jason.encode!(payload), headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to POST /package: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
Logger.info("we got some data as follows. #{inspect(data)}")
|
||||
|
||||
formatted = case data do
|
||||
{:ok, %{"jobId" => package_job_id}} ->
|
||||
{:ok, package_job_id}
|
||||
end
|
||||
|
||||
Logger.info("start_package has finished it's duties and is returning the following formatted data.")
|
||||
Logger.info(inspect(formatted))
|
||||
|
||||
formatted
|
||||
|
||||
end
|
||||
|
||||
defp poll_job_completion(job_id) do
|
||||
Logger.info("Polling job completion for Job ID #{job_id}")
|
||||
|
||||
poll_interval = 5_000
|
||||
max_retries = 999
|
||||
|
||||
Enum.reduce_while(1..max_retries, :ok, fn _, acc ->
|
||||
case get_job_status(job_id) do
|
||||
{:ok, "completed", data} ->
|
||||
Logger.info("Job ID #{job_id} completed successfully")
|
||||
Logger.info("here we need to return {:ok, asset_id}")
|
||||
Logger.info(inspect(data))
|
||||
formatted = case data do
|
||||
{:ok, %{"outputData" => outputData}} ->
|
||||
case Jason.decode(outputData) do
|
||||
{:ok, decoded} -> decoded
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to decode outputData: #{inspect(reason)}")
|
||||
%{}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
Logger.info(">>>> formatted=#{inspect(formatted)}")
|
||||
{:halt, {:ok, formatted["assetId"]}}
|
||||
|
||||
{:ok, "failed", _data} ->
|
||||
{:halt, {:error, "superstreamer reports that the job failed."}}
|
||||
|
||||
{:ok, state, data} ->
|
||||
Logger.info("Job ID #{job_id} #{state}. Re-polling in #{poll_interval}.")
|
||||
:timer.sleep(poll_interval)
|
||||
{:cont, acc}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Error polling job ID #{job_id}: #{inspect(reason)}")
|
||||
{:halt, {:error, reason}}
|
||||
send(job_pid, {:complete, hls_video})
|
||||
rescue
|
||||
e ->
|
||||
send(job_pid, {:error, e, job})
|
||||
reraise e, __STACKTRACE__
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_job_status(job_id) do
|
||||
headers = auth_headers()
|
||||
defp await_transmuxer(vod, stage \\ :retrieving, done \\ 0) do
|
||||
receive do
|
||||
{:progress, %{stage: stage_now, done: done_now, total: total}} ->
|
||||
Streams.broadcast_processing_progressed!(stage, vod, min(1, done / total))
|
||||
done_total = if(stage == stage_now, do: done, else: 0)
|
||||
await_transmuxer(vod, stage_now, done_total + done_now)
|
||||
|
||||
data = case HTTPoison.get("#{@superstreamer_url}/jobs/#{job_id}", headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
{:complete, vod} ->
|
||||
Streams.broadcast_processing_progressed!(stage, vod, 1)
|
||||
Streams.broadcast_processing_completed!(:upload, vod, vod.url)
|
||||
{:ok, vod.url}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to GET /jobs/<job_id>: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
status = case data do
|
||||
{:ok, %{"state" => state}} ->
|
||||
{:ok, state, data}
|
||||
end
|
||||
|
||||
Logger.info("job #{job_id} status=#{inspect(status)}")
|
||||
|
||||
status
|
||||
|
||||
end
|
||||
|
||||
|
||||
defp update_vod_with_playlist_url(vod, asset_id) do
|
||||
playlist_url = generate_playlist_url(asset_id)
|
||||
Logger.info("playlist_url=#{playlist_url}")
|
||||
vod
|
||||
|> Ecto.Changeset.change(playlist_url: playlist_url)
|
||||
|> Repo.update!()
|
||||
end
|
||||
|
||||
defp generate_playlist_url(asset_id), do: "#{@public_s3_endpoint}/package/#{asset_id}/hls/master.m3u8"
|
||||
|
||||
defp auth_headers do
|
||||
[
|
||||
{"authorization", "Bearer #{@auth_token}"},
|
||||
{"content-type", "application/json"}
|
||||
]
|
||||
{:error, e, %Oban.Job{attempt: attempt, max_attempts: max_attempts}} ->
|
||||
Streams.broadcast_processing_failed!(vod, attempt, max_attempts)
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
|
||||
|
||||
|
||||
defmodule Bright.ObanWorkers.CreateHlsPlaylist do
|
||||
use Oban.Worker, queue: :default, max_attempts: 6
|
||||
|
||||
alias Bright.Repo
|
||||
alias Bright.Streams.Vod
|
||||
|
||||
require Logger
|
||||
|
||||
|
||||
|
||||
# args: %{"vod_id" => 10, "input_url" => "http://38.242.193.246:8081/fixtures/2024-12-19T03-10-30Z.ts"}
|
||||
|
||||
@impl Oban.Worker
|
||||
def perform(%Oban.Job{args: %{"vod_id" => vod_id}}) do
|
||||
Application.get_env(:bright, :superstreamer_url) || raise("superstreamer_url missing from app config")
|
||||
Logger.info(">>>> create_hls_playlist is performing. vod_id=#{vod_id}")
|
||||
vod = Repo.get!(Vod, vod_id)
|
||||
|
||||
payload = build_payload(vod.origin_temp_input_url)
|
||||
|
||||
Logger.info("Starting transcoding for VOD ID #{vod_id}")
|
||||
|
||||
with {:ok, transcode_job_id} <- start_transcode(payload),
|
||||
{:ok, asset_id} <- poll_job_completion(transcode_job_id),
|
||||
{:ok, package_job_id} <- start_package(asset_id),
|
||||
{:ok, asset_id} <- poll_job_completion(package_job_id) do
|
||||
update_vod_with_playlist_url(vod, asset_id)
|
||||
|
||||
Logger.info("HLS playlist created and updated for VOD ID #{vod_id}")
|
||||
else
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to create HLS playlist for VOD ID #{vod_id}: #{inspect(reason)}")
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_payload(input_url) do
|
||||
%{
|
||||
"inputs" => [
|
||||
%{"type" => "audio", "path" => input_url, "language" => "eng"},
|
||||
%{"type" => "video", "path" => input_url}
|
||||
],
|
||||
"streams" => [
|
||||
%{"type" => "video", "codec" => "h264", "height" => 1080},
|
||||
# %{"type" => "video", "codec" => "h264", "height" => 720}, # when I enabled this, I see a superstreamer error? -- "header 'content-length' is listed in signed headers, but is not present "
|
||||
%{"type" => "video", "codec" => "h264", "height" => 144},
|
||||
%{"type" => "audio", "codec" => "aac"}
|
||||
],
|
||||
"tag" => "create_hls_playlist"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
defp start_transcode(payload) do
|
||||
Logger.info("Starting transcode with payload: #{inspect(payload)}")
|
||||
IO.puts "Starting transcode with payload: #{inspect(payload)}"
|
||||
|
||||
headers = auth_headers()
|
||||
|
||||
Logger.info("auth headers as follows")
|
||||
Logger.info(inspect(headers))
|
||||
superstreamer_url = Application.get_env(:bright, :superstreamer_url)
|
||||
|
||||
|
||||
Logger.info("now we will POST /transcode to superstreamer_url=#{superstreamer_url}")
|
||||
data = case HTTPoison.post("#{superstreamer_url}/transcode", Jason.encode!(payload), headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
[] ->
|
||||
{:error, "We got an empty response from Superstreamer"}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to POST /transcode: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
Logger.info("we got some data as follows. #{inspect(data)}")
|
||||
|
||||
formatted = case data do
|
||||
{:ok, %{"jobId" => transcode_job_id}} ->
|
||||
{:ok, transcode_job_id}
|
||||
end
|
||||
|
||||
Logger.info("start_transcode has finished it's duties and is returning the following formatted data.")
|
||||
Logger.info(inspect(formatted))
|
||||
|
||||
formatted
|
||||
|
||||
end
|
||||
|
||||
defp start_package(asset_id) do
|
||||
superstreamer_url = Application.get_env(:bright, :superstreamer_url)
|
||||
payload = %{
|
||||
"assetId" => asset_id,
|
||||
"concurrency" => 1,
|
||||
"public" => false
|
||||
}
|
||||
|
||||
Logger.info("Starting packaging for asset ID #{asset_id}")
|
||||
|
||||
headers = auth_headers()
|
||||
|
||||
Logger.info("auth headers as follows")
|
||||
Logger.info(inspect(headers))
|
||||
|
||||
Logger.info("now we will POST /package to superstreamer_url=#{superstreamer_url}")
|
||||
data = case HTTPoison.post("#{superstreamer_url}/package", Jason.encode!(payload), headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to POST /package: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
Logger.info("we got some data as follows. #{inspect(data)}")
|
||||
|
||||
formatted = case data do
|
||||
{:ok, %{"jobId" => package_job_id}} ->
|
||||
{:ok, package_job_id}
|
||||
end
|
||||
|
||||
Logger.info("start_package has finished it's duties and is returning the following formatted data.")
|
||||
Logger.info(inspect(formatted))
|
||||
|
||||
formatted
|
||||
|
||||
end
|
||||
|
||||
defp poll_job_completion(job_id) do
|
||||
Logger.info("Polling job completion for Job ID #{job_id}")
|
||||
|
||||
poll_interval = 5_000
|
||||
max_retries = 999
|
||||
|
||||
Enum.reduce_while(1..max_retries, :ok, fn _, acc ->
|
||||
case get_job_status(job_id) do
|
||||
{:ok, "completed", data} ->
|
||||
Logger.info("Job ID #{job_id} completed successfully")
|
||||
Logger.info("here we need to return {:ok, asset_id}")
|
||||
Logger.info(inspect(data))
|
||||
formatted = case data do
|
||||
{:ok, %{"outputData" => outputData}} ->
|
||||
case Jason.decode(outputData) do
|
||||
{:ok, decoded} -> decoded
|
||||
{:error, reason} ->
|
||||
Logger.error("Failed to decode outputData: #{inspect(reason)}")
|
||||
%{}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
Logger.info(">>>> formatted=#{inspect(formatted)}")
|
||||
{:halt, {:ok, formatted["assetId"]}}
|
||||
|
||||
{:ok, "failed", _data} ->
|
||||
{:halt, {:error, "superstreamer reports that the job failed."}}
|
||||
|
||||
{:ok, state, data} ->
|
||||
Logger.info("Job ID #{job_id} #{state}. Re-polling in #{poll_interval}.")
|
||||
:timer.sleep(poll_interval)
|
||||
{:cont, acc}
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.error("Error polling job ID #{job_id}: #{inspect(reason)}")
|
||||
{:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_job_status(job_id) do
|
||||
headers = auth_headers()
|
||||
|
||||
data = case HTTPoison.get("#{Application.get_env(:bright, :superstreamer_url)}/jobs/#{job_id}", headers) do
|
||||
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
|
||||
{:ok, Jason.decode!(body)}
|
||||
|
||||
{:ok, %HTTPoison.Response{status_code: status, body: body}} ->
|
||||
{:error, %{status: status, body: body}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, reason}
|
||||
|
||||
failed ->
|
||||
Logger.error("Failed to GET /jobs/<job_id>: #{inspect(failed)}")
|
||||
{:error, :failed}
|
||||
end
|
||||
|
||||
status = case data do
|
||||
{:ok, %{"state" => state}} ->
|
||||
{:ok, state, data}
|
||||
end
|
||||
|
||||
Logger.info("job #{job_id} status=#{inspect(status)}")
|
||||
|
||||
status
|
||||
|
||||
end
|
||||
|
||||
|
||||
defp update_vod_with_playlist_url(vod, asset_id) do
|
||||
playlist_url = generate_playlist_url(asset_id)
|
||||
Logger.info("playlist_url=#{playlist_url}")
|
||||
vod
|
||||
|> Ecto.Changeset.change(playlist_url: playlist_url)
|
||||
|> Repo.update!()
|
||||
end
|
||||
|
||||
defp generate_playlist_url(asset_id) do
|
||||
public_s3_endpoint = Application.get_env(:bright, :public_s3_endpoint) || raise("public_s3_endpoint was nil")
|
||||
"#{public_s3_endpoint}/package/#{asset_id}/hls/master.m3u8"
|
||||
end
|
||||
|
||||
defp auth_headers do
|
||||
superstreamer_auth_token = Application.get_env(:bright, :superstreamer_auth_token) || raise("superstreamer_auth_token was nil")
|
||||
[
|
||||
{"authorization", "Bearer #{superstreamer_auth_token}"},
|
||||
{"content-type", "application/json"}
|
||||
]
|
||||
end
|
||||
end
|
|
@ -28,7 +28,6 @@ defmodule Bright.ObanWorkers.CreateThumbnail do
|
|||
{:ok, %{output: output, filename: output_file}} <- Images.create_thumbnail(local_filename),
|
||||
{:ok, s3Asset} <- B2.put(output_file)
|
||||
do
|
||||
IO.puts("updating vod ...")
|
||||
update_vod_with_thumbnail_url(vod, s3Asset.cdn_url)
|
||||
else
|
||||
{:error, reason} ->
|
||||
|
@ -41,16 +40,8 @@ defmodule Bright.ObanWorkers.CreateThumbnail do
|
|||
|
||||
defp generate_thumbnail_url(basename), do: "#{@public_s3_endpoint}/#{basename}"
|
||||
|
||||
# defp update_vod_with_thumbnail_url(vod, thumbnail_url) do
|
||||
# IO.puts "thumbnail_url=#{thumbnail_url}"
|
||||
# vod
|
||||
# |> Ecto.Changeset.change(thumbnail_url: thumbnail_url)
|
||||
# |> Repo.update!()
|
||||
|
||||
# end
|
||||
|
||||
defp update_vod_with_thumbnail_url(vod, thumbnail_url) do
|
||||
IO.puts "thumbnail_url=#{thumbnail_url}"
|
||||
case Repo.update(vod |> Ecto.Changeset.change(thumbnail_url: thumbnail_url)) do
|
||||
{:ok, updated_vod} -> {:ok, updated_vod}
|
||||
{:error, changeset} -> {:error, changeset}
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
defmodule Bright.Orders do
|
||||
@moduledoc """
|
||||
The Orders context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Bright.Repo
|
||||
|
||||
alias Bright.Orders.{Order,LineItem}
|
||||
alias Bright.ShoppingCart
|
||||
|
||||
@doc """
|
||||
Returns the list of orders.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_orders()
|
||||
[%Order{}, ...]
|
||||
|
||||
"""
|
||||
def list_orders do
|
||||
Repo.all(Order)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single order.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Order does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_order!(123)
|
||||
%Order{}
|
||||
|
||||
iex> get_order!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
# def get_order!(id), do: Repo.get!(Order, id)
|
||||
def get_order!(user_uuid, id) do
|
||||
Order
|
||||
|> Repo.get_by!(id: id, user_uuid: user_uuid)
|
||||
|> Repo.preload([line_items: [:product]])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_order(%{field: value})
|
||||
{:ok, %Order{}}
|
||||
|
||||
iex> create_order(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_order(attrs \\ %{}) do
|
||||
%Order{}
|
||||
|> Order.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_order(order, %{field: new_value})
|
||||
{:ok, %Order{}}
|
||||
|
||||
iex> update_order(order, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_order(%Order{} = order, attrs) do
|
||||
order
|
||||
|> Order.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a order.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_order(order)
|
||||
{:ok, %Order{}}
|
||||
|
||||
iex> delete_order(order)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_order(%Order{} = order) do
|
||||
Repo.delete(order)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking order changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_order(order)
|
||||
%Ecto.Changeset{data: %Order{}}
|
||||
|
||||
"""
|
||||
def change_order(%Order{} = order, attrs \\ %{}) do
|
||||
Order.changeset(order, attrs)
|
||||
end
|
||||
|
||||
alias Bright.Orders.LineItem
|
||||
|
||||
@doc """
|
||||
Returns the list of order_line_items.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_order_line_items()
|
||||
[%LineItem{}, ...]
|
||||
|
||||
"""
|
||||
def list_order_line_items do
|
||||
Repo.all(LineItem)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single line_item.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Line item does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_line_item!(123)
|
||||
%LineItem{}
|
||||
|
||||
iex> get_line_item!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_line_item!(id), do: Repo.get!(LineItem, id)
|
||||
|
||||
@doc """
|
||||
Creates a line_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_line_item(%{field: value})
|
||||
{:ok, %LineItem{}}
|
||||
|
||||
iex> create_line_item(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_line_item(attrs \\ %{}) do
|
||||
%LineItem{}
|
||||
|> LineItem.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a line_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_line_item(line_item, %{field: new_value})
|
||||
{:ok, %LineItem{}}
|
||||
|
||||
iex> update_line_item(line_item, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_line_item(%LineItem{} = line_item, attrs) do
|
||||
line_item
|
||||
|> LineItem.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a line_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_line_item(line_item)
|
||||
{:ok, %LineItem{}}
|
||||
|
||||
iex> delete_line_item(line_item)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_line_item(%LineItem{} = line_item) do
|
||||
Repo.delete(line_item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking line_item changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_line_item(line_item)
|
||||
%Ecto.Changeset{data: %LineItem{}}
|
||||
|
||||
"""
|
||||
def change_line_item(%LineItem{} = line_item, attrs \\ %{}) do
|
||||
LineItem.changeset(line_item, attrs)
|
||||
end
|
||||
|
||||
def complete_order(%ShoppingCart.Cart{} = cart) do
|
||||
line_items =
|
||||
Enum.map(cart.items, fn item ->
|
||||
%{product_id: item.product_id, price: item.product.price, quantity: item.quantity}
|
||||
end)
|
||||
|
||||
order =
|
||||
Ecto.Changeset.change(%Order{},
|
||||
user_uuid: cart.user_uuid,
|
||||
total_price: ShoppingCart.total_cart_price(cart),
|
||||
line_items: line_items
|
||||
)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.insert(:order, order)
|
||||
|> Ecto.Multi.run(:prune_cart, fn _repo, _changes ->
|
||||
ShoppingCart.prune_cart_items(cart)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{order: order}} -> {:ok, order}
|
||||
{:error, name, value, _changes_so_far} -> {:error, {name, value}}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
defmodule Bright.Orders.LineItem do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "order_line_items" do
|
||||
field :price, :decimal
|
||||
field :quantity, :integer
|
||||
|
||||
belongs_to :order, Bright.Orders.Order
|
||||
belongs_to :product, Bright.Catalog.Product
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(line_item, attrs) do
|
||||
line_item
|
||||
|> cast(attrs, [:price, :quantity])
|
||||
|> validate_required([:price, :quantity])
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
defmodule Bright.Orders.Order do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "orders" do
|
||||
field :user_uuid, Ecto.UUID
|
||||
field :total_price, :decimal
|
||||
|
||||
has_many :line_items, Bright.Orders.LineItem
|
||||
has_many :products, through: [:line_items, :product]
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(order, attrs) do
|
||||
order
|
||||
|> cast(attrs, [:user_uuid, :total_price])
|
||||
|> validate_required([:user_uuid, :total_price])
|
||||
end
|
||||
end
|
|
@ -1,272 +0,0 @@
|
|||
defmodule Bright.ShoppingCart do
|
||||
@moduledoc """
|
||||
The ShoppingCart context.
|
||||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Bright.Repo
|
||||
alias Bright.Catalog
|
||||
alias Bright.ShoppingCart.{Cart, CartItem}
|
||||
|
||||
@doc """
|
||||
Returns the list of carts.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_carts()
|
||||
[%Cart{}, ...]
|
||||
|
||||
"""
|
||||
def list_carts do
|
||||
Repo.all(Cart)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single cart.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Cart does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_cart!(123)
|
||||
%Cart{}
|
||||
|
||||
iex> get_cart!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_cart!(id), do: Repo.get!(Cart, id)
|
||||
|
||||
@doc """
|
||||
Creates a cart.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_cart(%{field: value})
|
||||
{:ok, %Cart{}}
|
||||
|
||||
iex> create_cart(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_cart(user_uuid) do
|
||||
%Cart{user_uuid: user_uuid}
|
||||
|> Cart.changeset(%{})
|
||||
|> Repo.insert()
|
||||
|> case do
|
||||
{:ok, cart} -> {:ok, reload_cart(cart)}
|
||||
{:error, changeset} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
def reload_cart(%Cart{} = cart), do: get_cart_by_user_uuid(cart.user_uuid)
|
||||
|
||||
def add_item_to_cart(%Cart{} = cart, product_id) do
|
||||
product = Catalog.get_product!(product_id)
|
||||
%CartItem{quantity: 1, price_when_carted: product.price}
|
||||
|> CartItem.changeset(%{})
|
||||
|> Ecto.Changeset.put_assoc(:cart, cart)
|
||||
|> Ecto.Changeset.put_assoc(:product, product)
|
||||
|> Repo.insert(
|
||||
on_conflict: [inc: [quantity: 1]],
|
||||
conflict_target: [:cart_id, :product_id]
|
||||
)
|
||||
end
|
||||
|
||||
def remove_item_from_cart(%Cart{} = cart, product_id) do
|
||||
{1, _} =
|
||||
Repo.delete_all(
|
||||
from(i in CartItem,
|
||||
where: i.cart_id == ^cart.id,
|
||||
where: i.product_id == ^product_id
|
||||
)
|
||||
)
|
||||
|
||||
{:ok, reload_cart(cart)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a cart.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_cart(cart, %{field: new_value})
|
||||
{:ok, %Cart{}}
|
||||
|
||||
iex> update_cart(cart, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_cart(%Cart{} = cart, attrs) do
|
||||
changeset =
|
||||
cart
|
||||
|> Cart.changeset(attrs)
|
||||
|> Ecto.Changeset.cast_assoc(:items, with: &CartItem.changeset/2)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:cart, changeset)
|
||||
|> Ecto.Multi.delete_all(:discarded_items, fn %{cart: cart} ->
|
||||
from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0)
|
||||
end)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{cart: cart}} -> {:ok, cart}
|
||||
{:error, :cart, changeset, _changes_so_far} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a cart.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_cart(cart)
|
||||
{:ok, %Cart{}}
|
||||
|
||||
iex> delete_cart(cart)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_cart(%Cart{} = cart) do
|
||||
Repo.delete(cart)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking cart changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_cart(cart)
|
||||
%Ecto.Changeset{data: %Cart{}}
|
||||
|
||||
"""
|
||||
def change_cart(%Cart{} = cart, attrs \\ %{}) do
|
||||
Cart.changeset(cart, attrs)
|
||||
end
|
||||
|
||||
alias Bright.ShoppingCart.CartItem
|
||||
|
||||
@doc """
|
||||
Returns the list of cart_items.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> list_cart_items()
|
||||
[%CartItem{}, ...]
|
||||
|
||||
"""
|
||||
def list_cart_items do
|
||||
Repo.all(CartItem)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a single cart_item.
|
||||
|
||||
Raises `Ecto.NoResultsError` if the Cart item does not exist.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_cart_item!(123)
|
||||
%CartItem{}
|
||||
|
||||
iex> get_cart_item!(456)
|
||||
** (Ecto.NoResultsError)
|
||||
|
||||
"""
|
||||
def get_cart_item!(id), do: Repo.get!(CartItem, id)
|
||||
|
||||
@doc """
|
||||
Creates a cart_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create_cart_item(%{field: value})
|
||||
{:ok, %CartItem{}}
|
||||
|
||||
iex> create_cart_item(%{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_cart_item(attrs \\ %{}) do
|
||||
%CartItem{}
|
||||
|> CartItem.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a cart_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_cart_item(cart_item, %{field: new_value})
|
||||
{:ok, %CartItem{}}
|
||||
|
||||
iex> update_cart_item(cart_item, %{field: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_cart_item(%CartItem{} = cart_item, attrs) do
|
||||
cart_item
|
||||
|> CartItem.changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a cart_item.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> delete_cart_item(cart_item)
|
||||
{:ok, %CartItem{}}
|
||||
|
||||
iex> delete_cart_item(cart_item)
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def delete_cart_item(%CartItem{} = cart_item) do
|
||||
Repo.delete(cart_item)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking cart_item changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_cart_item(cart_item)
|
||||
%Ecto.Changeset{data: %CartItem{}}
|
||||
|
||||
"""
|
||||
def change_cart_item(%CartItem{} = cart_item, attrs \\ %{}) do
|
||||
CartItem.changeset(cart_item, attrs)
|
||||
end
|
||||
|
||||
def get_cart_by_user_uuid(user_uuid) do
|
||||
Repo.one(
|
||||
from(c in Cart,
|
||||
where: c.user_uuid == ^user_uuid,
|
||||
left_join: i in assoc(c, :items),
|
||||
left_join: p in assoc(i, :product),
|
||||
order_by: [asc: i.inserted_at],
|
||||
preload: [items: {i, product: p}]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def total_item_price(%CartItem{} = item) do
|
||||
Decimal.mult(item.product.price, item.quantity)
|
||||
end
|
||||
|
||||
def total_cart_price(%Cart{} = cart) do
|
||||
Enum.reduce(cart.items, 0, fn item, acc ->
|
||||
item
|
||||
|> total_item_price()
|
||||
|> Decimal.add(acc)
|
||||
end)
|
||||
end
|
||||
|
||||
def prune_cart_items(%Cart{} = cart) do
|
||||
{_, _} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id))
|
||||
{:ok, reload_cart(cart)}
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,82 @@
|
|||
defmodule Bright.Storage do
|
||||
def endpoint_url do
|
||||
%{scheme: scheme, host: host} = Application.fetch_env!(:ex_aws, :s3) |> Enum.into(%{})
|
||||
"#{scheme}#{host}"
|
||||
end
|
||||
|
||||
def bucket(), do: Bright.config([:buckets, :media])
|
||||
|
||||
def to_absolute(type, uuid, uri) do
|
||||
if URI.parse(uri).scheme do
|
||||
uri
|
||||
else
|
||||
to_absolute_uri(type, uuid, uri)
|
||||
end
|
||||
end
|
||||
|
||||
defp to_absolute_uri(:video, uuid, uri),
|
||||
do: "#{endpoint_url()}/#{bucket()}/#{uuid}/#{uri}"
|
||||
|
||||
defp to_absolute_uri(:clip, uuid, uri),
|
||||
do: "#{endpoint_url()}/#{bucket()}/clips/#{uuid}/#{uri}"
|
||||
|
||||
def upload_to_bucket(contents, remote_path, bucket, opts \\ []) do
|
||||
op = Bright.config([:buckets, bucket]) |> ExAws.S3.put_object(remote_path, contents, opts)
|
||||
ExAws.request(op, [])
|
||||
end
|
||||
|
||||
def upload_from_filename_to_bucket(
|
||||
local_path,
|
||||
remote_path,
|
||||
bucket,
|
||||
cb \\ fn _ -> nil end,
|
||||
opts \\ []
|
||||
) do
|
||||
%{size: size} = File.stat!(local_path)
|
||||
|
||||
chunk_size = 5 * 1024 * 1024
|
||||
|
||||
ExAws.S3.Upload.stream_file(local_path, [{:chunk_size, chunk_size}])
|
||||
|> Stream.map(fn chunk ->
|
||||
cb.(%{stage: :persisting, done: chunk_size, total: size})
|
||||
chunk
|
||||
end)
|
||||
|> ExAws.S3.upload(Bright.config([:buckets, bucket]), remote_path, opts)
|
||||
|> ExAws.request([])
|
||||
end
|
||||
|
||||
def upload(contents, remote_path, opts \\ []) do
|
||||
upload_to_bucket(contents, remote_path, :media, opts)
|
||||
end
|
||||
|
||||
def upload_from_filename(local_path, remote_path, cb \\ fn _ -> nil end, opts \\ []) do
|
||||
upload_from_filename_to_bucket(
|
||||
local_path,
|
||||
remote_path,
|
||||
:media,
|
||||
cb,
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
def update_object!(bucket, object, opts) do
|
||||
bucket = Bright.config([:buckets, bucket])
|
||||
|
||||
with {:ok, %{body: body}} <- ExAws.S3.get_object(bucket, object) |> ExAws.request(),
|
||||
{:ok, res} <- ExAws.S3.put_object(bucket, object, body, opts) |> ExAws.request() do
|
||||
res
|
||||
else
|
||||
err -> err
|
||||
end
|
||||
end
|
||||
|
||||
def remove(remote_path, opts \\ []) do
|
||||
remove_from_bucket(remote_path, :media, opts)
|
||||
end
|
||||
|
||||
def remove_from_bucket(remote_path, bucket, opts) do
|
||||
ExAws.S3.delete_object(Bright.config([:buckets, bucket]), remote_path, opts)
|
||||
|> ExAws.request([])
|
||||
end
|
||||
|
||||
end
|
|
@ -11,6 +11,13 @@ defmodule Bright.Streams do
|
|||
alias Bright.Vtubers.Vtuber
|
||||
alias Bright.Tags.Tag
|
||||
alias Bright.Platforms.Platform
|
||||
alias Bright.{
|
||||
Cache,
|
||||
Events,
|
||||
Downloader,
|
||||
Storage,
|
||||
}
|
||||
|
||||
|
||||
@doc """
|
||||
Returns the list of streams.
|
||||
|
@ -271,4 +278,175 @@ defmodule Bright.Streams do
|
|||
def change_vod(%Vod{} = vod, attrs \\ %{}) do
|
||||
Vod.changeset(vod, attrs)
|
||||
end
|
||||
|
||||
def transmux_to_hls(%Vod{} = vod, cb) do
|
||||
|
||||
if !vod.origin_temp_input_url, do: raise("vod was missing origin_temp_input_url")
|
||||
|
||||
local_path = Cache.generate_filename(vod.origin_temp_input_url)
|
||||
Downloader.download!(vod.origin_temp_input_url, local_path)
|
||||
|
||||
IO.puts "transmuxing to hls using origin_temp_input_url=#{vod.origin_temp_input_url}, local_path=#{local_path}"
|
||||
|
||||
|
||||
master_pl_name = "master.m3u8"
|
||||
|
||||
dir_name = "vod-#{vod.id}"
|
||||
dir = Path.join(Bright.Cache.cache_dir, dir_name)
|
||||
File.mkdir_p!(dir)
|
||||
|
||||
cb.(%{stage: :transmuxing, done: 1, total: 1})
|
||||
|
||||
# @see https://www.mux.com/articles/how-to-convert-mp4-to-hls-format-with-ffmpeg-a-step-by-step-guide#when-to-use-hls-over-mp4-formats-whats-the-difference
|
||||
# ffmpeg -i input_video.mp4 \
|
||||
# -filter_complex \
|
||||
# "[0:v]split=3[v1][v2][v3]; \
|
||||
# [v1]scale=w=1920:h=1080[v1out]; \
|
||||
# [v2]scale=w=1280:h=720[v2out]; \
|
||||
# [v3]scale=w=854:h=480[v3out]" \
|
||||
# -map "[v1out]" -c:v:0 libx264 -b:v:0 5000k -maxrate:v:0 5350k -bufsize:v:0 7500k \
|
||||
# -map "[v2out]" -c:v:1 libx264 -b:v:1 2800k -maxrate:v:1 2996k -bufsize:v:1 4200k \
|
||||
# -map "[v3out]" -c:v:2 libx264 -b:v:2 1400k -maxrate:v:2 1498k -bufsize:v:2 2100k \
|
||||
# -map a:0 -c:a aac -b:a:0 192k -ac 2 \
|
||||
# -map a:0 -c:a aac -b:a:1 128k -ac 2 \
|
||||
# -map a:0 -c:a aac -b:a:2 96k -ac 2 \
|
||||
# -f hls \
|
||||
# -hls_time 10 \
|
||||
# -hls_playlist_type vod \
|
||||
# -hls_flags independent_segments \
|
||||
# -hls_segment_type mpegts \
|
||||
# -hls_segment_filename stream_%v/data%03d.ts \
|
||||
# -master_pl_name master.m3u8 \
|
||||
# -var_stream_map "v:0,a:0 v:1,a:1 v:2,a:2" \
|
||||
# stream_%v/playlist.m3u8
|
||||
|
||||
|
||||
System.cmd("ffmpeg", [
|
||||
"-i",
|
||||
local_path,
|
||||
"-filter_complex",
|
||||
"[0:v]split=5[v1][v2][v3][v4][v5];" <>
|
||||
"[v1]scale=w=1920:h=1080[v1out];" <>
|
||||
"[v2]scale=w=1280:h=720[v2out];" <>
|
||||
"[v3]scale=w=854:h=480[v3out];" <>
|
||||
"[v4]scale=w=640:h=360[v4out];" <>
|
||||
"[v5]scale=w=284:h=160[v5out]",
|
||||
|
||||
# Video streams
|
||||
"-map", "[v1out]", "-c:v:0", "libx264", "-b:v:0", "5000k", "-maxrate:v:0", "5350k", "-bufsize:v:0", "7500k",
|
||||
"-map", "[v2out]", "-c:v:1", "libx264", "-b:v:1", "2800k", "-maxrate:v:1", "2996k", "-bufsize:v:1", "4200k",
|
||||
"-map", "[v3out]", "-c:v:2", "libx264", "-b:v:2", "1400k", "-maxrate:v:2", "1498k", "-bufsize:v:2", "2100k",
|
||||
"-map", "[v4out]", "-c:v:3", "libx264", "-b:v:3", "800k", "-maxrate:v:3", "856k", "-bufsize:v:3", "1200k",
|
||||
"-map", "[v5out]", "-c:v:4", "libx264", "-b:v:4", "300k", "-maxrate:v:4", "300k", "-bufsize:v:4", "480k",
|
||||
|
||||
# Audio streams
|
||||
"-map", "a:0", "-c:a:0", "aac", "-b:a:0", "192k", "-ac:a:0", "2",
|
||||
"-map", "a:0", "-c:a:1", "aac", "-b:a:1", "192k", "-ac:a:1", "2",
|
||||
"-map", "a:0", "-c:a:2", "aac", "-b:a:2", "192k", "-ac:a:2", "2",
|
||||
"-map", "a:0", "-c:a:3", "aac", "-b:a:3", "164k", "-ac:a:3", "2",
|
||||
"-map", "a:0", "-c:a:4", "aac", "-b:a:4", "164k", "-ac:a:4", "2",
|
||||
|
||||
"-f", "hls",
|
||||
"-hls_time", "2",
|
||||
"-hls_playlist_type", "vod",
|
||||
"-hls_flags", "independent_segments",
|
||||
"-hls_segment_type", "mpegts",
|
||||
"-start_number", "0",
|
||||
"-hls_list_size", "0",
|
||||
"-hls_segment_filename", "#{dir}/stream_%v_segment_%d.ts",
|
||||
"-master_pl_name", master_pl_name,
|
||||
"-var_stream_map", "v:0,a:0 v:1,a:1 v:2,a:2 v:3,a:3 v:4,a:4",
|
||||
"#{dir}/stream_%v.m3u8"
|
||||
])
|
||||
|
||||
files = Path.wildcard("#{dir}/*")
|
||||
|
||||
files
|
||||
|> Elixir.Stream.map(fn hls_local_path ->
|
||||
cb.(%{stage: :persisting, done: 1, total: length(files)})
|
||||
hls_local_path
|
||||
end)
|
||||
|> Enum.each(fn hls_local_path ->
|
||||
Storage.upload_from_filename(
|
||||
hls_local_path,
|
||||
"package/vod-#{vod.id}/#{Path.basename(hls_local_path)}",
|
||||
cb,
|
||||
content_type:
|
||||
if(String.ends_with?(hls_local_path, ".m3u8"),
|
||||
do: "application/x-mpegURL",
|
||||
else: "video/mp4"
|
||||
)
|
||||
)
|
||||
end)
|
||||
|
||||
playlist_url = "#{Bright.config([:s3_cdn_endpoint])}/package/vod-#{vod.id}/master.m3u8"
|
||||
IO.puts "playlist_url=#{playlist_url} local_path=#{local_path}"
|
||||
|
||||
|
||||
hls_vod = update_vod(vod, %{
|
||||
playlist_url: playlist_url,
|
||||
local_path: local_path
|
||||
})
|
||||
|
||||
IO.puts inspect(hls_vod)
|
||||
|
||||
cb.(%{stage: :generating_thumbnail, done: 1, total: 1})
|
||||
# {:ok, hls_vod} = store_thumbnail_from_file(hls_vod, vod.local_path)
|
||||
|
||||
# @TODO should probably keep the file cached locally for awhile for any additional processing
|
||||
# File.rm!(hls_vod.local_path)
|
||||
|
||||
hls_vod
|
||||
end
|
||||
|
||||
defp thumbnail_filename(%Vod{} = vod) do
|
||||
"vod-#{vod.id}-index.jpeg"
|
||||
end
|
||||
|
||||
def store_thumbnail_from_file(%Vod{} = vod, src_path, marker \\ %{minutes: 0}, opts \\ []) do
|
||||
with {:ok, thumbnail} <- create_thumbnail_from_file(vod, src_path, marker, opts),
|
||||
{:ok, %{key: key, cdn_url: cdn_url}} <- B2.put(thumbnail, thumbnail_filename(vod)) do
|
||||
{:ok, vod_thumbnail} =
|
||||
Vod
|
||||
|> change_vod(%{
|
||||
thumbnail_url: thumbnail_filename(vod)
|
||||
})
|
||||
|> Repo.insert(on_conflict: :nothing)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
defp create_thumbnail_from_file(%Vod{} = vod, src_path, marker, opts \\ []) do
|
||||
dst_path = Path.join(System.tmp_dir!(), "#{vod.id}-#{marker.minutes}.jpeg")
|
||||
|
||||
if not File.exists?(dst_path) do
|
||||
:ok = Thumbnex.create_thumbnail(src_path, dst_path, opts)
|
||||
end
|
||||
|
||||
File.read(dst_path)
|
||||
end
|
||||
|
||||
|
||||
|
||||
defp broadcast!(topic, msg) do
|
||||
Phoenix.PubSub.broadcast!(@pubsub, topic, {__MODULE__, msg})
|
||||
end
|
||||
|
||||
def broadcast_processing_progressed!(stage, vod, pct) do
|
||||
broadcast!("backend", %Events.ProcessingProgressed{vod: vod, stage: stage, pct: pct})
|
||||
end
|
||||
|
||||
def broadcast_processing_completed!(action, vod, url) do
|
||||
broadcast!("backend", %Events.ProcessingCompleted{action: action, vod: vod, url: url})
|
||||
end
|
||||
|
||||
def broadcast_processing_failed!(vod, attempt, max_attempts) do
|
||||
broadcast!("backend", %Events.ProcessingFailed{
|
||||
vod: vod,
|
||||
attempt: attempt,
|
||||
max_attempts: max_attempts
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -12,8 +12,10 @@ defmodule Bright.Streams.Vod do
|
|||
field :torrent, :string
|
||||
field :notes, :string
|
||||
field :thumbnail_url, :string
|
||||
field :local_path, :string
|
||||
|
||||
belongs_to :stream, Bright.Streams.Stream
|
||||
# belongs_to :uploader, Bright.Accounts.User, foreign_key: :uploaded_by_id # Metadata for uploader
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
@ -21,7 +23,7 @@ defmodule Bright.Streams.Vod do
|
|||
@doc false
|
||||
def changeset(vod, attrs) do
|
||||
vod
|
||||
|> cast(attrs, [:s3_cdn_url, :mux_asset_id, :mux_playback_id, :ipfs_cid, :torrent, :stream_id, :origin_temp_input_url, :playlist_url, :thumbnail_url])
|
||||
|> cast(attrs, [:local_path, :s3_cdn_url, :mux_asset_id, :mux_playback_id, :ipfs_cid, :torrent, :stream_id, :origin_temp_input_url, :playlist_url, :thumbnail_url])
|
||||
|> validate_required([:stream_id])
|
||||
end
|
||||
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
defmodule Bright.User do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Bright.{Repo, Regexp}
|
||||
|
||||
schema "users" do
|
||||
field :name, :string
|
||||
field :is_admin, :boolean
|
||||
field :auth_token, :string
|
||||
field :auth_token_expires_at, :utc_datetime
|
||||
field :signed_in_at, :utc_datetime
|
||||
field :joined_at, :utc_datetime
|
||||
field :patreon_handle, :string
|
||||
field :github_handle, :string
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@doc false
|
||||
def changeset(user, attrs) do
|
||||
user
|
||||
|> cast(attrs, [:name, :patreon_handle, :github_handle, :is_admin])
|
||||
|> validate_required([:name, :patreon_handle])
|
||||
end
|
||||
|
||||
defp changeset_with_allowed_attrs(user, attrs, allowed) do
|
||||
user
|
||||
|> cast(attrs, allowed)
|
||||
|> validate_required([:name])
|
||||
|> validate_format(:name, Regexp.name(), message: Regexp.name_message())
|
||||
|> validate_length(:name, max: 40, message: "max 40 chars")
|
||||
|> validate_format(:github_handle, Regexp.social(), message: Regexp.social_message())
|
||||
|> validate_format(:patreon_handle, Regexp.social(), message: Regexp.social_message())
|
||||
|> unique_constraint(:github_handle)
|
||||
|> unique_constraint(:patreon_handle)
|
||||
end
|
||||
|
||||
|
||||
def auth_changeset(user, attrs \\ %{}),
|
||||
do: cast(user, attrs, ~w(auth_token auth_token_expires_at)a)
|
||||
|
||||
def update_changeset(user, attrs \\ %{}) do
|
||||
user
|
||||
|> insert_changeset(attrs)
|
||||
end
|
||||
|
||||
def refresh_auth_token(user, expires_in \\ 60 * 24) do
|
||||
auth_token = Base.encode16(:crypto.strong_rand_bytes(8))
|
||||
expires_at = Timex.add(Timex.now(), Timex.Duration.from_minutes(expires_in))
|
||||
|
||||
changeset =
|
||||
auth_changeset(user, %{auth_token: auth_token, auth_token_expires_at: expires_at})
|
||||
|
||||
{:ok, user} = Repo.update(changeset)
|
||||
user
|
||||
end
|
||||
|
||||
def insert_changeset(user, attrs \\ %{}) do
|
||||
allowed = ~w(name github_handle patreon_handle)a
|
||||
changeset_with_allowed_attrs(user, attrs, allowed)
|
||||
end
|
||||
|
||||
# def join(conn = %{method: "POST"}, params = %{"user" => user_params}) do
|
||||
# changeset = User.insert_changeset(%User{}, user_params)
|
||||
|
||||
# case Repo.insert(changeset) do
|
||||
# {:ok, user} ->
|
||||
# welcome_community(conn, user)
|
||||
|
||||
# {:error, changeset} ->
|
||||
# conn
|
||||
# |> put_flash(:error, "Something went wrong. 😭")
|
||||
# |> render(:join, changeset: changeset, user: nil)
|
||||
# end
|
||||
# end
|
||||
|
||||
|
||||
# def create_from_ueberauth(%{provider: :github, info: %{nickname: handle}}) do
|
||||
# changeset = User.insert_changeset(%User{}, %{github_handle: handle, patreon_handle: nil, name: handle})
|
||||
|
||||
# case Repo.insert(changeset) do
|
||||
# {:ok, user} -> {:ok, user}
|
||||
# {:error, changeset} -> {:error, changeset}
|
||||
# end
|
||||
# end
|
||||
|
||||
def get!(id) do
|
||||
User
|
||||
|> Repo.get(id)
|
||||
end
|
||||
|
||||
|
||||
def get_by_ueberauth(%{provider: :github, info: %{nickname: handle}}) do
|
||||
Repo.get_by(__MODULE__, github_handle: handle)
|
||||
end
|
||||
|
||||
def get_by_ueberauth(%{provider: :patreon, info: %{id: patreon_id}}) do
|
||||
Repo.get_by(__MODULE__, patreon_handle: patreon_id)
|
||||
end
|
||||
|
||||
def get_by_ueberauth(_), do: nil
|
||||
|
||||
def sign_in_changes(user) do
|
||||
change(user, %{
|
||||
auth_token: nil,
|
||||
auth_token_expires_at: nil,
|
||||
signed_in_at: now_in_seconds(),
|
||||
joined_at: user.joined_at || now_in_seconds()
|
||||
})
|
||||
end
|
||||
|
||||
defp now_in_seconds, do: Timex.now() |> DateTime.truncate(:second)
|
||||
|
||||
|
||||
def vod_count(user) do
|
||||
user
|
||||
|> Vod.authored_by()
|
||||
|> Vod.published()
|
||||
|> Repo.count()
|
||||
end
|
||||
|
||||
def tag_count(user) do
|
||||
user
|
||||
|> Tag.authored_by()
|
||||
|> Tag.published()
|
||||
|> Repo.count()
|
||||
end
|
||||
|
||||
def timestamp_count(user) do
|
||||
user
|
||||
|> Timestamp.authored_by()
|
||||
|> Timestamp.published()
|
||||
|> Repo.count()
|
||||
end
|
||||
|
||||
def stream_count(user) do
|
||||
user
|
||||
|> Stream.authored_by()
|
||||
|> Stream.published()
|
||||
|> Repo.count()
|
||||
end
|
||||
|
||||
end
|
|
@ -1,70 +0,0 @@
|
|||
defmodule Bright.UserFromAuth do
|
||||
@moduledoc """
|
||||
Retrieve the user information from an auth request
|
||||
"""
|
||||
require Logger
|
||||
require Jason
|
||||
|
||||
alias Ueberauth.Auth
|
||||
|
||||
def find_or_create(%Auth{provider: :identity} = auth) do
|
||||
case validate_pass(auth.credentials) do
|
||||
:ok ->
|
||||
{:ok, basic_info(auth)}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
def find_or_create(%Auth{} = auth) do
|
||||
{:ok, basic_info(auth)}
|
||||
end
|
||||
|
||||
# github does it this way
|
||||
defp avatar_from_auth(%{info: %{urls: %{avatar_url: image}}}), do: image
|
||||
|
||||
# facebook does it this way
|
||||
defp avatar_from_auth(%{info: %{image: image}}), do: image
|
||||
|
||||
# default case if nothing matches
|
||||
defp avatar_from_auth(auth) do
|
||||
Logger.warning("#{auth.provider} needs to find an avatar URL!")
|
||||
Logger.debug(Jason.encode!(auth))
|
||||
nil
|
||||
end
|
||||
|
||||
defp basic_info(auth) do
|
||||
%{id: auth.uid, name: name_from_auth(auth), avatar: avatar_from_auth(auth)}
|
||||
end
|
||||
|
||||
defp name_from_auth(auth) do
|
||||
if auth.info.name do
|
||||
auth.info.name
|
||||
else
|
||||
name =
|
||||
[auth.info.first_name, auth.info.last_name]
|
||||
|> Enum.filter(&(&1 != nil and &1 != ""))
|
||||
|
||||
if Enum.empty?(name) do
|
||||
auth.info.nickname
|
||||
else
|
||||
Enum.join(name, " ")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_pass(%{other: %{password: nil}}) do
|
||||
{:error, "Password required"}
|
||||
end
|
||||
|
||||
defp validate_pass(%{other: %{password: pw, password_confirmation: pw}}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp validate_pass(%{other: %{password: _}}) do
|
||||
{:error, "Passwords do not match"}
|
||||
end
|
||||
|
||||
defp validate_pass(_), do: {:error, "Password Required"}
|
||||
end
|
|
@ -102,9 +102,9 @@ defmodule Bright.Vtubers do
|
|||
Vtuber.changeset(vtuber, attrs)
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Bright.User do
|
||||
def to_string(%Bright.User{name: name}) when not is_nil(name), do: name
|
||||
def to_string(%Bright.User{}), do: "Anonymous"
|
||||
end
|
||||
# defimpl String.Chars, for: Bright.Auth.User do
|
||||
# def to_string(%Bright.Auth.User{name: name}) when not is_nil(name), do: name
|
||||
# def to_string(%Bright.Auth.User{}), do: "Anonymous"
|
||||
# end
|
||||
|
||||
end
|
||||
|
|
|
@ -118,11 +118,13 @@
|
|||
</.link>
|
||||
<% else %>
|
||||
<.link
|
||||
href={~p"/users/register"}
|
||||
href={~p"/auth/github"}
|
||||
method="get"
|
||||
class="navbar-item"
|
||||
>
|
||||
Register
|
||||
Sign in via GH
|
||||
</.link>
|
||||
<%# <p>hello</p> %>
|
||||
<%# <.link
|
||||
href={~p"/auth/github"}
|
||||
class="navbar-item"
|
||||
|
@ -139,6 +141,49 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
<ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
|
||||
<%= if @current_user do %>
|
||||
<li class="text-[0.8125rem] leading-6 text-zinc-900">
|
||||
<figure class="image is-32x32">
|
||||
<img alt={@current_user.name} class="is-rounded" src={@current_user.avatar} />
|
||||
</figure>
|
||||
</li>
|
||||
<li>
|
||||
<.link
|
||||
href={~p"/users/settings"}
|
||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
||||
>
|
||||
Settings
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<.link
|
||||
href={~p"/users/log_out"}
|
||||
method="delete"
|
||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
||||
>
|
||||
Log out
|
||||
</.link>
|
||||
</li>
|
||||
<% else %>
|
||||
<li>
|
||||
<.link
|
||||
href={~p"/users/register"}
|
||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
||||
>
|
||||
Register
|
||||
</.link>
|
||||
</li>
|
||||
<li>
|
||||
<.link
|
||||
href={~p"/users/log_in"}
|
||||
class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
|
||||
>
|
||||
Log in
|
||||
</.link>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
<%= @inner_content %>
|
||||
</body>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
defmodule BrightWeb.AuthHTML do
|
||||
use BrightWeb, :html
|
||||
|
||||
|
||||
embed_templates "auth_html/*"
|
||||
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
<p>hello this is request.html</p>
|
|
@ -1,32 +0,0 @@
|
|||
defmodule BrightWeb.CartController do
|
||||
use BrightWeb, :controller
|
||||
alias Bright.ShoppingCart
|
||||
def show(conn, _params) do
|
||||
render(conn, :show, changeset: ShoppingCart.change_cart(conn.assigns.cart))
|
||||
# render(conn, :show)
|
||||
end
|
||||
def update(conn, %{"cart" => cart_params}) do
|
||||
case ShoppingCart.update_cart(conn.assigns.cart, cart_params) do
|
||||
{:ok, _cart} ->
|
||||
redirect(conn, to: ~p"/cart")
|
||||
|
||||
{:error, _changeset} ->
|
||||
conn
|
||||
|> put_flash(:error, "There was an error updating your cart")
|
||||
|> redirect(to: ~p"/cart")
|
||||
end
|
||||
end
|
||||
end
|
||||
# def show(conn, %{"id" => id}) do
|
||||
# product = Catalog.get_product!(id)
|
||||
# render(conn, :show, product: product)
|
||||
# end
|
||||
|
||||
# def show(conn, %{"id" => id}) do
|
||||
# stream =
|
||||
# id
|
||||
# |> Streams.get_stream!()
|
||||
# |> Streams.inc_page_views()
|
||||
|
||||
# render(conn, :show, stream: stream)
|
||||
# end
|
|
@ -1,10 +0,0 @@
|
|||
defmodule BrightWeb.CartHTML do
|
||||
use BrightWeb, :html
|
||||
|
||||
# this alias is for the html.heex templates
|
||||
alias Bright.ShoppingCart
|
||||
|
||||
embed_templates "cart_html/*"
|
||||
|
||||
def currency_to_str(%Decimal{} = val), do: "$#{Decimal.round(val, 2)}"
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
|
||||
<.header>
|
||||
My Cart
|
||||
<:subtitle :if={@cart.items == []}>Your cart is empty</:subtitle>
|
||||
<:actions>
|
||||
<.link href={~p"/orders"} method="post">
|
||||
<.button>Complete order</.button>
|
||||
</.link>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<div :if={@cart.items !== []}>
|
||||
<.simple_form :let={f} for={@changeset} action={~p"/cart"}>
|
||||
<.inputs_for :let={%{data: item} = item_form} field={f[:items]}>
|
||||
<.input field={item_form[:quantity]} type="number" label={item.product.title} />
|
||||
{currency_to_str(ShoppingCart.total_item_price(item))}
|
||||
</.inputs_for>
|
||||
<:actions>
|
||||
<.button>Update cart</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
<b>Total</b>: {currency_to_str(ShoppingCart.total_cart_price(@cart))}
|
||||
</div>
|
||||
|
||||
<.back navigate={~p"/products"}>Back to products</.back>
|
|
@ -1,22 +0,0 @@
|
|||
defmodule BrightWeb.CartItemController do
|
||||
use BrightWeb, :controller
|
||||
alias Bright.ShoppingCart
|
||||
|
||||
def create(conn, %{"product_id" => product_id}) do
|
||||
case ShoppingCart.add_item_to_cart(conn.assigns.cart, product_id) do
|
||||
{:ok, _item} ->
|
||||
conn
|
||||
|> put_flash(:info, "Item added to your cart")
|
||||
|> redirect(to: ~p"/cart")
|
||||
{:error, _changeset} ->
|
||||
conn
|
||||
|> put_flash(:eerror, "There was an error adding the item to your cart")
|
||||
|> redirect(to: ~p"/cart")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => product_id}) do
|
||||
{:ok, _cart} = ShoppingCart.remove_item_from_cart(conn.assigns.cart, product_id)
|
||||
redirect(conn, to: ~p"/cart")
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
defmodule BrightWeb.HelloController do
|
||||
use BrightWeb, :controller
|
||||
|
||||
# plug :put_view, html: HelloWeb.PageHTML, json: HelloWeb.PageJSON
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, :index)
|
||||
end
|
||||
|
||||
|
||||
def show(conn, %{"messenger" => messenger}) do
|
||||
conn
|
||||
|> assign(:messenger, messenger)
|
||||
|> assign(:receiver, "Dweezil")
|
||||
|> render(:show)
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
defmodule BrightWeb.HelloHTML do
|
||||
use BrightWeb, :html
|
||||
|
||||
embed_templates "hello_html/*"
|
||||
|
||||
attr :messenger, :string, required: true
|
||||
|
||||
def greet(assigns) do
|
||||
~H"""
|
||||
<h2>Hello World, from {@messenger}!</h2>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
<section>
|
||||
<h2>Hello World, from Phoenix!~</h2>
|
||||
</section>
|
|
@ -1,3 +0,0 @@
|
|||
<section>
|
||||
<.greet messenger={@messenger} />
|
||||
</section>
|
|
@ -1,21 +0,0 @@
|
|||
defmodule BrightWeb.OrderController do
|
||||
use BrightWeb, :controller
|
||||
alias Bright.Orders
|
||||
def create(conn, _) do
|
||||
case Orders.complete_order(conn.assigns.cart) do
|
||||
{:ok, order} ->
|
||||
conn
|
||||
|> put_flash(:info, "Order created successfully.")
|
||||
|> redirect(to: ~p"/orders/#{order}")
|
||||
|
||||
{:error, _reason} ->
|
||||
conn
|
||||
|> put_flash(:error, "There was an error processing your order")
|
||||
|> redirect(to: ~p"/cart")
|
||||
end
|
||||
end
|
||||
def show(conn, %{"id" => id}) do
|
||||
order = Orders.get_order!(conn.assigns.current_uuid, id)
|
||||
render(conn, :show, order: order)
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
defmodule BrightWeb.OrderHTML do
|
||||
use BrightWeb, :html
|
||||
embed_templates "order_html/*"
|
||||
end
|
|
@ -1,20 +0,0 @@
|
|||
<.header>
|
||||
Thank you for your order!
|
||||
<:subtitle>
|
||||
<strong>User uuid: </strong>{@order.user_uuid}
|
||||
</:subtitle>
|
||||
</.header>
|
||||
|
||||
|
||||
<.table id="items" rows={@order.line_items}>
|
||||
<:col :let={item} label="Title">{item.product.title}</:col>
|
||||
<:col :let={item} label="Quantity">{item.quantity}</:col>
|
||||
<:col :let={item} label="Price">
|
||||
{BrightWeb.CartHTML.currency_to_str(item.price)}
|
||||
</:col>
|
||||
</.table>
|
||||
|
||||
<strong>Total price:</strong>
|
||||
{BrightWeb.CartHTML.currency_to_str(@order.total_price)}
|
||||
|
||||
<.back navigate={~p"/products"}>Back to products</.back>
|
|
@ -6,10 +6,11 @@ defmodule BrightWeb.PageController do
|
|||
# so skip the default app layout.
|
||||
# render(conn, :home, layout: false)
|
||||
|
||||
# render(conn, "index.html", current_user: get_session(conn, :current_user))
|
||||
# send_resp(conn, 201, "")
|
||||
conn
|
||||
|> put_status(202)
|
||||
|> render(:home, layout: false)
|
||||
|> render(:home, layout: false, current_user: get_session(conn, :current_user))
|
||||
# redirect(conn, to: ~p"/redirect_test")
|
||||
# redirect(conn, external: "https://elixir-lang.org/")
|
||||
end
|
||||
|
@ -22,6 +23,10 @@ defmodule BrightWeb.PageController do
|
|||
render(conn, :api, layout: false)
|
||||
end
|
||||
|
||||
def profile(conn, _params) do
|
||||
render(conn, :profile, layout: false)
|
||||
end
|
||||
|
||||
def health(conn, _params) do
|
||||
data = %{message: "OK", status: "success"}
|
||||
json(conn, data)
|
||||
|
@ -30,4 +35,5 @@ defmodule BrightWeb.PageController do
|
|||
def redirect_test(conn, _params) do
|
||||
render(conn, :home, layout: false)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,10 +3,16 @@
|
|||
|
||||
<main class="container">
|
||||
|
||||
<div class="section">
|
||||
<h2 class="title is-2">About</h2>
|
||||
<section class="hero is-primary">
|
||||
<div class="hero-body">
|
||||
<p class="title">Dedication to the preservation of Lewdtuber history</p>
|
||||
<p class="subtitle"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p>Welcome to Futureporn, a platform built by fans, for fans, dedicated to preserving the moments that matter in the world of R-18 VTuber live streaming. It all started with a simple need: capturing ProjektMelody's streams on Chaturbate. Chaturbate doesn’t save VODs, and sometimes we missed the magic. Other times, creators like ProjektMelody faced unnecessary de-platforming for simply being unique. We wanted to create a space where this content could endure, unshaken by the tides of censorship or fleeting platforms.</p>
|
||||
<div class="section">
|
||||
|
||||
<p>A platform built by fans, for fans, dedicated to preserving the moments that matter in the world of R-18 VTuber live streaming. It all started with a simple need: capturing ProjektMelody's streams on Chaturbate. Chaturbate doesn’t save VODs, and sometimes we missed the magic. Other times, creators like ProjektMelody faced unnecessary de-platforming for simply being unique. We wanted to create a space where this content could endure, unshaken by the tides of censorship or fleeting platforms.</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
|
|
|
@ -26,20 +26,6 @@
|
|||
</div>
|
||||
|
||||
|
||||
<!--<%= inspect(@current_user) %>
|
||||
<%= if @current_user do %>
|
||||
<div>
|
||||
<h1>Welcome, <%= @current_user[:name] %>!</h1>
|
||||
<p>ID: <%= @current_user[:id] %></p>
|
||||
<p>Avatar: <%= @current_user[:avatar] %></p>
|
||||
|
||||
</div>
|
||||
<% else %>
|
||||
<div>
|
||||
<p>You are not logged in. Please <a href="/auth/github">log in</a>.</p>
|
||||
</div>
|
||||
<% end %>-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<.flash_group flash={@flash} />
|
||||
|
||||
|
||||
<%= if @current_user do%>
|
||||
<main class="section">
|
||||
<h2 class="title is-2">Profile</h2>
|
||||
<p class="subtitle">{@current_user.name}</p>
|
||||
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<section class="section">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img
|
||||
src={@current_user.avatar}
|
||||
alt={@current_user.name}
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{@current_user.name}</p>
|
||||
<p class="subtitle is-6">Github User {@current_user.github_id}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<p class="subtitle is-6">Futureporn User {@current_user.id}</p>
|
||||
<p class="subtitle is-6"><i>n</i> uploads</p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec
|
||||
iaculis mauris. <a>@bulmaio</a>. <a href="#">#css</a>
|
||||
<a href="#">#responsive</a>
|
||||
<br />
|
||||
<time datetime="2016-1-1">11:09 PM - 1 Jan 2016</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<% else %>
|
||||
<p>Please <.link href={~p"/auth/github"}>sign in</.link></p>
|
||||
<% end %>
|
|
@ -1,62 +0,0 @@
|
|||
defmodule BrightWeb.ProductController do
|
||||
use BrightWeb, :controller
|
||||
|
||||
alias Bright.Catalog
|
||||
alias Bright.Catalog.Product
|
||||
|
||||
def index(conn, _params) do
|
||||
products = Catalog.list_products()
|
||||
render(conn, :index, products: products)
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
changeset = Catalog.change_product(%Product{})
|
||||
render(conn, :new, changeset: changeset)
|
||||
end
|
||||
|
||||
def create(conn, %{"product" => product_params}) do
|
||||
case Catalog.create_product(product_params) do
|
||||
{:ok, product} ->
|
||||
conn
|
||||
|> put_flash(:info, "Product created successfully.")
|
||||
|> redirect(to: ~p"/products/#{product}")
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, :new, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def show(conn, %{"id" => id}) do
|
||||
product = Catalog.get_product!(id)
|
||||
render(conn, :show, product: product)
|
||||
end
|
||||
|
||||
def edit(conn, %{"id" => id}) do
|
||||
product = Catalog.get_product!(id)
|
||||
changeset = Catalog.change_product(product)
|
||||
render(conn, :edit, product: product, changeset: changeset)
|
||||
end
|
||||
|
||||
def update(conn, %{"id" => id, "product" => product_params}) do
|
||||
product = Catalog.get_product!(id)
|
||||
|
||||
case Catalog.update_product(product, product_params) do
|
||||
{:ok, product} ->
|
||||
conn
|
||||
|> put_flash(:info, "Product updated successfully.")
|
||||
|> redirect(to: ~p"/products/#{product}")
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
render(conn, :edit, product: product, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"id" => id}) do
|
||||
product = Catalog.get_product!(id)
|
||||
{:ok, _product} = Catalog.delete_product(product)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Product deleted successfully.")
|
||||
|> redirect(to: ~p"/products")
|
||||
end
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
defmodule BrightWeb.ProductHTML do
|
||||
use BrightWeb, :html
|
||||
|
||||
embed_templates "product_html/*"
|
||||
|
||||
@doc """
|
||||
Renders a product form.
|
||||
"""
|
||||
attr :changeset, Ecto.Changeset, required: true
|
||||
attr :action, :string, required: true
|
||||
|
||||
def product_form(assigns)
|
||||
|
||||
def category_opts(changeset) do
|
||||
existing_ids =
|
||||
changeset
|
||||
|> Ecto.Changeset.get_change(:categories, [])
|
||||
|> Enum.map(& &1.data.id)
|
||||
|
||||
for cat <- Bright.Catalog.list_categories(),
|
||||
do: [key: cat.title, value: cat.id, selected: cat.id in existing_ids]
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
<.header>
|
||||
Edit Product {@product.id}
|
||||
<:subtitle>Use this form to manage product records in the database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.product_form changeset={@changeset} action={~p"/products/#{@product}"} />
|
||||
|
||||
<.back navigate={~p"/products"}>Back to products</.back>
|
|
@ -1,26 +0,0 @@
|
|||
<.header>
|
||||
Listing Products
|
||||
<:actions>
|
||||
<.link href={~p"/products/new"}>
|
||||
<.button>New Product</.button>
|
||||
</.link>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table id="products" rows={@products} row_click={&JS.navigate(~p"/products/#{&1}")}>
|
||||
<:col :let={product} label="Title">{product.title}</:col>
|
||||
<:col :let={product} label="Description">{product.description}</:col>
|
||||
<:col :let={product} label="Price">{product.price}</:col>
|
||||
<:col :let={product} label="Views">{product.views}</:col>
|
||||
<:action :let={product}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/products/#{product}"}>Show</.link>
|
||||
</div>
|
||||
<.link navigate={~p"/products/#{product}/edit"}>Edit</.link>
|
||||
</:action>
|
||||
<:action :let={product}>
|
||||
<.link href={~p"/products/#{product}"} method="delete" data-confirm="Are you sure?">
|
||||
Delete
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
|
@ -1,8 +0,0 @@
|
|||
<.header>
|
||||
New Product
|
||||
<:subtitle>Use this form to manage product records in the database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.product_form changeset={@changeset} action={~p"/products"} />
|
||||
|
||||
<.back navigate={~p"/products"}>Back to products</.back>
|
|
@ -1,12 +0,0 @@
|
|||
<.simple_form :let={f} for={@changeset} action={@action}>
|
||||
<.error :if={@changeset.action}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
<.input field={f[:title]} type="text" label="Title" />
|
||||
<.input field={f[:description]} type="text" label="Description" />
|
||||
<.input field={f[:price]} type="number" label="Price" step="any" />
|
||||
<.input field={f[:category_ids]} type="select" multiple={true} options={category_opts(@changeset)} />
|
||||
<:actions>
|
||||
<.button>Save Product</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
|
@ -1,26 +0,0 @@
|
|||
<.header>
|
||||
Product {@product.id}
|
||||
<:subtitle>This is a product record from the database.</:subtitle>
|
||||
<:actions>
|
||||
<.link href={~p"/products/#{@product}/edit"}>
|
||||
<.button>Edit product</.button>
|
||||
</.link>
|
||||
<.link href={~p"/cart_items?product_id=#{@product.id}"} method="post">
|
||||
<.button>Add to cart</.button>
|
||||
</.link>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.list>
|
||||
<:item title="Title">{@product.title}</:item>
|
||||
<:item title="Description">{@product.description}</:item>
|
||||
<:item title="Price">{@product.price}</:item>
|
||||
<:item title="Views">{@product.views}</:item>
|
||||
<:item title="Categories">
|
||||
<ul>
|
||||
<li :for={cat <- @product.categories}>{cat.title}</li>
|
||||
</ul>
|
||||
</:item>
|
||||
</.list>
|
||||
|
||||
<.back navigate={~p"/products"}>Back to products</.back>
|
|
@ -1,59 +0,0 @@
|
|||
defmodule BrightWeb.UserController do
|
||||
use BrightWeb, :controller
|
||||
alias Bright.{User, Repo}
|
||||
require Logger
|
||||
|
||||
def index(conn, _params) do
|
||||
render(conn, :index)
|
||||
end
|
||||
|
||||
# def show(conn) do
|
||||
# conn
|
||||
# |> render(:show)
|
||||
# end
|
||||
|
||||
def show(conn = %{assigns: %{current_user: me}}, _params) do
|
||||
Logger.info(">>> me=#{inspect(me)}")
|
||||
render(conn, :show, changeset: User.update_changeset(me))
|
||||
end
|
||||
|
||||
# def show(conn) do
|
||||
# user = User.get_user!(id)
|
||||
# render(conn, :show, user: user)
|
||||
# end
|
||||
|
||||
|
||||
|
||||
def join(conn = %{method: "GET"}, params) do
|
||||
user = %User{
|
||||
name: Map.get(params, "name"),
|
||||
github_handle: Map.get(params, "github_handle"),
|
||||
patreon_handle: Map.get(params, "patreon_handle")
|
||||
}
|
||||
|
||||
render(conn, :join, changeset: User.insert_changeset(user), user: nil)
|
||||
end
|
||||
|
||||
def join(conn = %{method: "POST"}, params = %{"user" => user_params}) do
|
||||
changeset = User.insert_changeset(%User{}, user_params)
|
||||
|
||||
case Repo.insert(changeset) do
|
||||
{:ok, user} ->
|
||||
welcome_community(conn, user)
|
||||
|
||||
{:error, changeset} ->
|
||||
conn
|
||||
|> put_flash(:error, "Something went wrong. 😭")
|
||||
|> render(:join, changeset: changeset, user: nil)
|
||||
end
|
||||
end
|
||||
|
||||
defp welcome_community(conn, user) do
|
||||
user = User.refresh_auth_token(user)
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Welcome #{user}")
|
||||
|> redirect(to: ~p"/")
|
||||
end
|
||||
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
defmodule BrightWeb.UserHTML do
|
||||
use BrightWeb, :html
|
||||
embed_templates "user_html/*"
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
<.header>
|
||||
Join Futureporn
|
||||
</.header>
|
||||
|
||||
|
||||
<.user_form changeset={@changeset} action={~p"/join"} />
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
<.header>
|
||||
Visitor Profile
|
||||
</.header>
|
||||
|
||||
<%= if @current_user do %>
|
||||
<p>@current_user is {@current_user}</p>
|
||||
<% else %>
|
||||
<p>there is no @current_user</p>
|
||||
<% end %>
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<.simple_form :let={f} for={@changeset} action={@action}>
|
||||
<.error :if={@changeset.action}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
<.input field={f[:name]} type="text" label="Name" help="This name is displayed publicly to credit you for any contributions" />
|
||||
|
||||
|
||||
<:actions>
|
||||
<.button>Save User Profile</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
defmodule BrightWeb.UserSessionController do
|
||||
use BrightWeb, :controller
|
||||
|
||||
alias Bright.Accounts
|
||||
alias BrightWeb.UserAuth
|
||||
|
||||
def create(conn, %{"_action" => "registered"} = params) do
|
||||
create(conn, params, "Account created successfully!")
|
||||
end
|
||||
|
||||
def create(conn, %{"_action" => "password_updated"} = params) do
|
||||
conn
|
||||
|> put_session(:user_return_to, ~p"/users/settings")
|
||||
|> create(params, "Password updated successfully!")
|
||||
end
|
||||
|
||||
def create(conn, params) do
|
||||
create(conn, params, "Welcome back!")
|
||||
end
|
||||
|
||||
defp create(conn, %{"user" => user_params}, info) do
|
||||
%{"email" => email, "password" => password} = user_params
|
||||
|
||||
if user = Accounts.get_user_by_email_and_password(email, password) do
|
||||
conn
|
||||
|> put_flash(:info, info)
|
||||
|> UserAuth.log_in_user(user, user_params)
|
||||
else
|
||||
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
|
||||
conn
|
||||
|> put_flash(:error, "Invalid email or password")
|
||||
|> put_flash(:email, String.slice(email, 0, 160))
|
||||
|> redirect(to: ~p"/users/log_in")
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
conn
|
||||
|> put_flash(:info, "Logged out successfully.")
|
||||
|> UserAuth.log_out_user()
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@ defmodule BrightWeb.VodController do
|
|||
|
||||
alias Bright.Streams
|
||||
alias Bright.Streams.Vod
|
||||
require Logger
|
||||
|
||||
def index(conn, _params) do
|
||||
vods = Streams.list_vods()
|
||||
|
@ -15,6 +16,9 @@ defmodule BrightWeb.VodController do
|
|||
end
|
||||
|
||||
def create(conn, %{"vod" => vod_params}) do
|
||||
# current_user = get_session(conn, :current_user)
|
||||
# vod_params = Map.put(vod_params, "uploaded_by_id", current_user.id)
|
||||
# Logger.info("current_user.id=#{current_user.id}")
|
||||
case Streams.create_vod(vod_params) do
|
||||
{:ok, vod} ->
|
||||
conn
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
</.header>
|
||||
|
||||
<.table id="vods" rows={@vods} row_click={&JS.navigate(~p"/vods/#{&1}")}>
|
||||
<%# <:col :let={vod} label="Uploader">{vod.uploaded_by_id}</:col> %>
|
||||
<:col :let={vod} label="ID">{vod.id}</:col>
|
||||
<:col :let={vod} label="S3 CDN URL">{vod.s3_cdn_url}</:col>
|
||||
<:col :let={vod} label="Mux asset">{vod.mux_asset_id}</:col>
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
</script>
|
||||
|
||||
<.list>
|
||||
<%# <:item title="Uploader"><img src={@vod.uploaded_by_id} /></:item> %>
|
||||
<:item title="Source VOD File">
|
||||
<%= if @vod.s3_cdn_url do %>
|
||||
<a class="button is-secondary" href={@vod.s3_cdn_url} download={Path.basename(@vod.s3_cdn_url)}>
|
||||
|
@ -53,7 +54,7 @@
|
|||
</a>
|
||||
<% end %>
|
||||
</:item>
|
||||
<:item title="Thumbnail URL"><img src={@vod.thumbnail_url} /></:item>
|
||||
<:item title="Thumbnail"><img src={@vod.thumbnail_url} /></:item>
|
||||
<:item title="HLS Playlist URL">{@vod.playlist_url}</:item>
|
||||
<:item title="Torrent">{@vod.torrent}</:item>
|
||||
<:item title="Ipfs CID">{@vod.ipfs_cid}</:item>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<.simple_form :let={f} for={@changeset} action={@action}>
|
||||
|
||||
<.error :if={@changeset.action}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
@ -13,6 +14,7 @@
|
|||
<.input field={f[:stream_id]} type="select" label="Stream" multiple={false} options={stream_opts(@changeset)}/>
|
||||
|
||||
|
||||
|
||||
<:actions>
|
||||
<.button>Save Vod</.button>
|
||||
</:actions>
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
defmodule BrightWeb.PostLive.FormComponent do
|
||||
use BrightWeb, :live_component
|
||||
|
||||
alias Bright.Blog
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div>
|
||||
<.header>
|
||||
{@title}
|
||||
<:subtitle>Use this form to manage post records in the database.</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form
|
||||
for={@form}
|
||||
id="post-form"
|
||||
phx-target={@myself}
|
||||
phx-change="validate"
|
||||
phx-submit="save"
|
||||
>
|
||||
<.input field={@form[:title]} type="text" label="Title" />
|
||||
<.input field={@form[:body]} type="text" label="Body" />
|
||||
<:actions>
|
||||
<.button phx-disable-with="Saving...">Save Post</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def update(%{post: post} = assigns, socket) do
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign_new(:form, fn ->
|
||||
to_form(Blog.change_post(post))
|
||||
end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"post" => post_params}, socket) do
|
||||
changeset = Blog.change_post(socket.assigns.post, post_params)
|
||||
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
|
||||
end
|
||||
|
||||
def handle_event("save", %{"post" => post_params}, socket) do
|
||||
save_post(socket, socket.assigns.action, post_params)
|
||||
end
|
||||
|
||||
defp save_post(socket, :edit, post_params) do
|
||||
case Blog.update_post(socket.assigns.post, post_params) do
|
||||
{:ok, post} ->
|
||||
notify_parent({:saved, post})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Post updated successfully")
|
||||
|> push_patch(to: socket.assigns.patch)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, form: to_form(changeset))}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_post(socket, :new, post_params) do
|
||||
case Blog.create_post(post_params) do
|
||||
{:ok, post} ->
|
||||
notify_parent({:saved, post})
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Post created successfully")
|
||||
|> push_patch(to: socket.assigns.patch)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, form: to_form(changeset))}
|
||||
end
|
||||
end
|
||||
|
||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
defmodule BrightWeb.PostLive.Index do
|
||||
use BrightWeb, :live_view
|
||||
|
||||
alias Bright.Blog
|
||||
alias Bright.Blog.Post
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, stream(socket, :posts, Blog.list_posts())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
socket
|
||||
|> assign(:page_title, "Edit Post")
|
||||
|> assign(:post, Blog.get_post!(id))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "New Post")
|
||||
|> assign(:post, %Post{})
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Posts")
|
||||
|> assign(:post, nil)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({BrightWeb.PostLive.FormComponent, {:saved, post}}, socket) do
|
||||
{:noreply, stream_insert(socket, :posts, post)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
post = Blog.get_post!(id)
|
||||
{:ok, _} = Blog.delete_post(post)
|
||||
|
||||
{:noreply, stream_delete(socket, :posts, post)}
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
<.header>
|
||||
Listing Posts
|
||||
<:actions>
|
||||
<.link patch={~p"/posts/new"}>
|
||||
<.button>New Post</.button>
|
||||
</.link>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
<.table
|
||||
id="posts"
|
||||
rows={@streams.posts}
|
||||
row_click={fn {_id, post} -> JS.navigate(~p"/posts/#{post}") end}
|
||||
>
|
||||
<:col :let={{_id, post}} label="Title">{post.title}</:col>
|
||||
<:col :let={{_id, post}} label="Body">{post.body}</:col>
|
||||
<:action :let={{_id, post}}>
|
||||
<div class="sr-only">
|
||||
<.link navigate={~p"/posts/#{post}"}>Show</.link>
|
||||
</div>
|
||||
<.link patch={~p"/posts/#{post}/edit"}>Edit</.link>
|
||||
</:action>
|
||||
<:action :let={{id, post}}>
|
||||
<.link
|
||||
phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")}
|
||||
data-confirm="Are you sure?"
|
||||
>
|
||||
Delete
|
||||
</.link>
|
||||
</:action>
|
||||
</.table>
|
||||
|
||||
<.modal :if={@live_action in [:new, :edit]} id="post-modal" show on_cancel={JS.patch(~p"/posts")}>
|
||||
<.live_component
|
||||
module={BrightWeb.PostLive.FormComponent}
|
||||
id={@post.id || :new}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
post={@post}
|
||||
patch={~p"/posts"}
|
||||
/>
|
||||
</.modal>
|
|
@ -1,21 +0,0 @@
|
|||
defmodule BrightWeb.PostLive.Show do
|
||||
use BrightWeb, :live_view
|
||||
|
||||
alias Bright.Blog
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
||||
|> assign(:post, Blog.get_post!(id))}
|
||||
end
|
||||
|
||||
defp page_title(:show), do: "Show Post"
|
||||
defp page_title(:edit), do: "Edit Post"
|
||||
end
|
|
@ -1,34 +0,0 @@
|
|||
<.header>
|
||||
Post {@post.id}
|
||||
<:subtitle>This is a post record from the database.</:subtitle>
|
||||
<:actions>
|
||||
<.link patch={~p"/posts/#{@post}/show/edit"} phx-click={JS.push_focus()}>
|
||||
<.button>Edit post</.button>
|
||||
</.link>
|
||||
</:actions>
|
||||
</.header>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<.list>
|
||||
<:item title="Title">{@post.title}</:item>
|
||||
<:item title="Body">{@post.body}</:item>
|
||||
</.list>
|
||||
|
||||
<.back navigate={~p"/posts"}>Back to posts</.back>
|
||||
|
||||
<.modal :if={@live_action == :edit} id="post-modal" show on_cancel={JS.patch(~p"/posts/#{@post}")}>
|
||||
<.live_component
|
||||
module={BrightWeb.PostLive.FormComponent}
|
||||
id={@post.id}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
post={@post}
|
||||
patch={~p"/posts/#{@post}"}
|
||||
/>
|
||||
</.modal>
|
|
@ -1,19 +0,0 @@
|
|||
defmodule BrightWeb.ThermostatLive do
|
||||
use BrightWeb, :live_view
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
Current temperature: {@temperature}°F
|
||||
<button class="button" phx-click="inc_temperature">+</button>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
temperature = 70 # Let's assume a fixed temperature for now
|
||||
{:ok, assign(socket, :temperature, temperature)}
|
||||
end
|
||||
|
||||
def handle_event("inc_temperature", _params, socket) do
|
||||
{:noreply, update(socket, :temperature, &(&1 + 1))}
|
||||
end
|
||||
end
|
|
@ -1,7 +1,9 @@
|
|||
defmodule BrightWeb.Router do
|
||||
use BrightWeb, :router
|
||||
|
||||
import BrightWeb.AuthController
|
||||
import BrightWeb.UserAuth
|
||||
|
||||
import Oban.Web.Router
|
||||
|
||||
pipeline :browser do
|
||||
plug(:accepts, ["html", "json"])
|
||||
|
@ -13,29 +15,20 @@ defmodule BrightWeb.Router do
|
|||
plug(:fetch_current_user)
|
||||
end
|
||||
|
||||
defp fetch_current_user(conn, _) do
|
||||
if user_uuid = get_session(conn, :current_user) do
|
||||
assign(conn, :current_user, user_uuid)
|
||||
else
|
||||
conn
|
||||
|> assign(:current_user, nil)
|
||||
|> put_session(:current_user, nil)
|
||||
end
|
||||
end
|
||||
|
||||
pipeline :api do
|
||||
plug(:accepts, ["json"])
|
||||
end
|
||||
|
||||
scope "/" do
|
||||
pipe_through([:browser, :require_authenticated_user, :require_admin_user])
|
||||
## !!! DANGER, platforms must only be writable by admins, (unless we implement SVG sanitizing)
|
||||
get("/platforms/new", PlatformController, :new)
|
||||
post("/platforms", PlatformController, :create)
|
||||
get("/platforms/:id/edit", PlatformController, :edit)
|
||||
patch("/platforms/:id", PlatformController, :update)
|
||||
put("/platforms/:id", PlatformController, :update)
|
||||
end
|
||||
# scope "/" do
|
||||
# pipe_through([:browser, :require_auth, :require_admin_user])
|
||||
# ## !!! DANGER, platforms must only be writable by admins, (unless we implement SVG sanitizing)
|
||||
# get("/platforms/new", PlatformController, :new)
|
||||
# post("/platforms", PlatformController, :create)
|
||||
# get("/platforms/:id/edit", PlatformController, :edit)
|
||||
# patch("/platforms/:id", PlatformController, :update)
|
||||
# put("/platforms/:id", PlatformController, :update)
|
||||
# end
|
||||
|
||||
scope "/auth", BrightWeb do
|
||||
pipe_through(:browser)
|
||||
|
@ -46,8 +39,15 @@ defmodule BrightWeb.Router do
|
|||
delete("/logout", AuthController, :delete)
|
||||
end
|
||||
|
||||
|
||||
# scope "/account", BrightWeb do
|
||||
# pipe_through([:browser, :require_auth])
|
||||
|
||||
# post("/", AuthController, :create_and_sign_in)
|
||||
# end
|
||||
|
||||
scope "/" do
|
||||
pipe_through([:browser, :require_authenticated_user])
|
||||
pipe_through([:browser, :require_auth])
|
||||
|
||||
get("/streams/new", StreamController, :new)
|
||||
post("/streams", StreamController, :create)
|
||||
|
@ -77,7 +77,7 @@ defmodule BrightWeb.Router do
|
|||
|
||||
get("/", PageController, :home)
|
||||
|
||||
get("/profile", UserController, :show, as: :user)
|
||||
get("/profile", PageController, :profile)
|
||||
|
||||
get("/patrons", PatronController, :index)
|
||||
get("/about", PageController, :about)
|
||||
|
@ -109,6 +109,9 @@ defmodule BrightWeb.Router do
|
|||
get("/vods", VodController, :index)
|
||||
get("/vods/:id", VodController, :show)
|
||||
end
|
||||
|
||||
oban_dashboard "/oban"
|
||||
|
||||
end
|
||||
|
||||
# Other scopes may use custom stacks.
|
||||
|
@ -135,47 +138,5 @@ defmodule BrightWeb.Router do
|
|||
end
|
||||
end
|
||||
|
||||
## Authentication routes
|
||||
|
||||
scope "/", BrightWeb do
|
||||
pipe_through([:browser])
|
||||
end
|
||||
|
||||
## Authentication routes
|
||||
|
||||
# scope "/", BrightWeb do
|
||||
# pipe_through [:browser, :redirect_if_user_is_authenticated]
|
||||
|
||||
# live_session :redirect_if_user_is_authenticated,
|
||||
# on_mount: [{BrightWeb.UserAuth, :redirect_if_user_is_authenticated}] do
|
||||
# live "/users/register", UserRegistrationLive, :new
|
||||
# live "/users/log_in", UserLoginLive, :new
|
||||
# live "/users/reset_password", UserForgotPasswordLive, :new
|
||||
# live "/users/reset_password/:token", UserResetPasswordLive, :edit
|
||||
# end
|
||||
|
||||
# post "/users/log_in", UserSessionController, :create
|
||||
# end
|
||||
|
||||
# scope "/", BrightWeb do
|
||||
# pipe_through [:browser, :require_authenticated_user]
|
||||
|
||||
# live_session :require_authenticated_user,
|
||||
# on_mount: [{BrightWeb.UserAuth, :ensure_authenticated}] do
|
||||
# live "/users/settings", UserSettingsLive, :edit
|
||||
# live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
|
||||
# end
|
||||
# end
|
||||
|
||||
# scope "/", BrightWeb do
|
||||
# pipe_through [:browser]
|
||||
|
||||
# delete "/users/log_out", UserSessionController, :delete
|
||||
|
||||
# live_session :current_user,
|
||||
# on_mount: [{BrightWeb.UserAuth, :mount_current_user}] do
|
||||
# live "/users/confirm/:token", UserConfirmationLive, :edit
|
||||
# live "/users/confirm", UserConfirmationInstructionsLive, :new
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
|
||||
|
||||
defmodule BrightWeb.AuthController do
|
||||
@moduledoc """
|
||||
Auth controller responsible for handling Ueberauth responses
|
||||
"""
|
||||
defmodule BrightWeb.UserAuth do
|
||||
use BrightWeb, :verified_routes
|
||||
|
||||
require Logger
|
||||
use BrightWeb, :controller
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller
|
||||
|
||||
plug Ueberauth
|
||||
|
||||
alias Ueberauth.Strategy.Helpers
|
||||
alias Bright.{Repo, User}
|
||||
alias Bright.Accounts
|
||||
|
||||
# Make the remember me cookie valid for 60 days.
|
||||
# If you want bump or reduce this value, also change
|
||||
# the token expiry itself in UserToken.
|
||||
@max_age 60 * 60 * 24 * 60
|
||||
@remember_me_cookie "_bright_web_user_remember_me"
|
||||
@remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
|
||||
|
||||
@doc """
|
||||
Logs the user in.
|
||||
|
@ -68,56 +68,6 @@ defmodule BrightWeb.AuthController do
|
|||
|> clear_session()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Used for routes that require the user to be authenticated.
|
||||
|
||||
If you want to enforce the user email is confirmed before
|
||||
they use the application at all, here would be a good place.
|
||||
"""
|
||||
def require_authenticated_user(conn, _opts) do
|
||||
if conn.assigns[:current_user] do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "You must log in to access this page.")
|
||||
|> maybe_store_return_to()
|
||||
|> redirect(to: ~p"/auth/github")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Used for routes that require the user to be an administrator.
|
||||
"""
|
||||
def require_admin_user(conn, _opts) do
|
||||
Logger.info("con.assigns[:current_user] as follows. #{inspect(conn.assigns)}")
|
||||
|
||||
case conn.assigns[:current_user] do
|
||||
%User{is_admin: true} -> # Assuming the user struct has an `is_admin` field
|
||||
conn
|
||||
|
||||
%User{} ->
|
||||
conn
|
||||
|> put_flash(:error, "You do not have permission to access this page.")
|
||||
|> redirect(to: ~p"/")
|
||||
|> halt()
|
||||
|
||||
nil ->
|
||||
conn
|
||||
|> put_flash(:error, "You must log in to access this page.")
|
||||
|> maybe_store_return_to()
|
||||
|> redirect(to: ~p"/auth/github")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_store_return_to(%{method: "GET"} = conn) do
|
||||
put_session(conn, :user_return_to, current_path(conn))
|
||||
end
|
||||
|
||||
defp maybe_store_return_to(conn), do: conn
|
||||
|
||||
|
||||
@doc """
|
||||
Logs the user out.
|
||||
|
||||
|
@ -137,14 +87,18 @@ defmodule BrightWeb.AuthController do
|
|||
|> redirect(to: ~p"/")
|
||||
end
|
||||
|
||||
# def fetch_current_user(conn) do
|
||||
# conn
|
||||
# |> get_session(:user_id)
|
||||
# |> case do
|
||||
# nil -> nil
|
||||
# user_id -> User.get(user_id)
|
||||
# end
|
||||
# end
|
||||
@doc """
|
||||
Authenticates the user by looking into the session
|
||||
and remember me token.
|
||||
"""
|
||||
def fetch_current_user(conn, _opts) do
|
||||
{user_token, conn} = ensure_user_token(conn)
|
||||
Logger.info("user_token=#{inspect(user_token)}")
|
||||
user = user_token && Accounts.get_user_by_session_token(user_token)
|
||||
Logger.info("fetch_current_user BEGIN. user=#{inspect(user)}")
|
||||
|
||||
assign(conn, :current_user, user)
|
||||
end
|
||||
|
||||
defp ensure_user_token(conn) do
|
||||
if token = get_session(conn, :user_token) do
|
||||
|
@ -160,6 +114,108 @@ defmodule BrightWeb.AuthController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Handles mounting and authenticating the current_user in LiveViews.
|
||||
|
||||
## `on_mount` arguments
|
||||
|
||||
* `:mount_current_user` - Assigns current_user
|
||||
to socket assigns based on user_token, or nil if
|
||||
there's no user_token or no matching user.
|
||||
|
||||
* `:ensure_authenticated` - Authenticates the user from the session,
|
||||
and assigns the current_user to socket assigns based
|
||||
on user_token.
|
||||
Redirects to login page if there's no logged user.
|
||||
|
||||
* `:redirect_if_user_is_authenticated` - Authenticates the user from the session.
|
||||
Redirects to signed_in_path if there's a logged user.
|
||||
|
||||
## Examples
|
||||
|
||||
Use the `on_mount` lifecycle macro in LiveViews to mount or authenticate
|
||||
the current_user:
|
||||
|
||||
defmodule BrightWeb.PageLive do
|
||||
use BrightWeb, :live_view
|
||||
|
||||
on_mount {BrightWeb.UserAuth, :mount_current_user}
|
||||
...
|
||||
end
|
||||
|
||||
Or use the `live_session` of your router to invoke the on_mount callback:
|
||||
|
||||
live_session :authenticated, on_mount: [{BrightWeb.UserAuth, :ensure_authenticated}] do
|
||||
live "/profile", ProfileLive, :index
|
||||
end
|
||||
"""
|
||||
def on_mount(:mount_current_user, _params, session, socket) do
|
||||
{:cont, mount_current_user(socket, session)}
|
||||
end
|
||||
|
||||
def on_mount(:ensure_authenticated, _params, session, socket) do
|
||||
socket = mount_current_user(socket, session)
|
||||
|
||||
if socket.assigns.current_user do
|
||||
{:cont, socket}
|
||||
else
|
||||
socket =
|
||||
socket
|
||||
|> Phoenix.LiveView.put_flash(:error, "You must log in to access this page.")
|
||||
|> Phoenix.LiveView.redirect(to: ~p"/auth/github")
|
||||
|
||||
{:halt, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def on_mount(:redirect_if_user_is_authenticated, _params, session, socket) do
|
||||
socket = mount_current_user(socket, session)
|
||||
|
||||
if socket.assigns.current_user do
|
||||
{:halt, Phoenix.LiveView.redirect(socket, to: signed_in_path(socket))}
|
||||
else
|
||||
{:cont, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp mount_current_user(socket, session) do
|
||||
Phoenix.Component.assign_new(socket, :current_user, fn ->
|
||||
if user_token = session["user_token"] do
|
||||
Accounts.get_user_by_session_token(user_token)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Used for routes that require the user to not be authenticated.
|
||||
"""
|
||||
def redirect_if_user_is_authenticated(conn, _opts) do
|
||||
if conn.assigns[:current_user] do
|
||||
conn
|
||||
|> redirect(to: signed_in_path(conn))
|
||||
|> halt()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Used for routes that require the user to be authenticated.
|
||||
|
||||
If you want to enforce the user email is confirmed before
|
||||
they use the application at all, here would be a good place.
|
||||
"""
|
||||
def require_auth(conn, _opts) do
|
||||
if conn.assigns[:current_user] do
|
||||
conn
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "You must log in to access this page.")
|
||||
|> maybe_store_return_to()
|
||||
|> redirect(to: ~p"/auth/github")
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
||||
defp put_token_in_session(conn, token) do
|
||||
conn
|
||||
|
@ -167,101 +223,11 @@ defmodule BrightWeb.AuthController do
|
|||
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|
||||
end
|
||||
|
||||
|
||||
|
||||
def create(conn = %{method: "POST"}, %{"token" => token}) do
|
||||
user = User.get_by_encoded_auth(token)
|
||||
|
||||
if user && Timex.before?(Timex.now(), user.auth_token_expires_at) do
|
||||
sign_in_and_redirect(conn, user, ~p"/~")
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "Whoops!")
|
||||
|> render("new.html", user: nil)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
conn
|
||||
|> put_flash(:info, "You have been logged out!")
|
||||
|> clear_session()
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
|
||||
conn
|
||||
|> put_flash(:error, "Failed to authenticate.")
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
|
||||
|
||||
def callback(conn = %{assigns: %{ueberauth_auth: auth}}, _params) do
|
||||
|
||||
user_params = %{
|
||||
github_handle: Map.get(auth, "nickname", nil),
|
||||
patreon_handle: Map.get(auth, "full_name", nil),
|
||||
name: "test"
|
||||
}
|
||||
|
||||
changeset = User.insert_changeset(%User{}, user_params)
|
||||
|
||||
case Repo.insert(changeset) do
|
||||
{:ok, user} ->
|
||||
UserAuth.log_in_user(conn, user)
|
||||
|
||||
{:error, changeset} ->
|
||||
conn
|
||||
|> put_flash(:error, "Something went wrong. 😭")
|
||||
|> render(:join, changeset: changeset, user: nil)
|
||||
end
|
||||
|
||||
# case User.get_by_ueberauth(auth) do
|
||||
# %User{} = user ->
|
||||
# UserAuth.log_in_user(conn, user, %{})
|
||||
|
||||
|
||||
# nil ->
|
||||
# case User.create_from_ueberauth(auth) do
|
||||
# {:ok, %User{} = user} ->
|
||||
# UserAuth.log_in_user(conn, user, %{})
|
||||
|
||||
# {:error, changeset} ->
|
||||
# Logger.error("failed to create user. auth=#{inspect(auth)}")
|
||||
# conn
|
||||
# |> put_flash(:error, "Failed to create user")
|
||||
# |> redirect(to: ~p"/")
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
|
||||
|
||||
defp sign_in_and_redirect(conn, user, route) do
|
||||
Logger.info("sign_in_and_redirect with user=#{inspect(user)}")
|
||||
|
||||
user
|
||||
|> User.sign_in_changes()
|
||||
|> Repo.update()
|
||||
|
||||
conn
|
||||
|> assign(:current_user, user)
|
||||
|> put_flash(:success, "Welcome to Futureporn!")
|
||||
|> put_session("id", user.id)
|
||||
|> configure_session(renew: true)
|
||||
|> redirect(to: route)
|
||||
end
|
||||
|
||||
|
||||
defp params_from_ueberauth(%{provider: :github, info: info}) do
|
||||
%{name: info.name, handle: info.nickname, github_handle: info.nickname, github_id: info.uid}
|
||||
end
|
||||
|
||||
defp params_from_ueberauth(%{provider: :patreon, info: info}) do
|
||||
%{name: info.name, handle: info.nickname, patreon_handle: info.full_name, patreon_id: info.id}
|
||||
defp maybe_store_return_to(%{method: "GET"} = conn) do
|
||||
put_session(conn, :user_return_to, current_path(conn))
|
||||
end
|
||||
|
||||
defp maybe_store_return_to(conn), do: conn
|
||||
|
||||
defp signed_in_path(_conn), do: ~p"/"
|
||||
|
||||
end
|
|
@ -54,7 +54,8 @@ defmodule Bright.MixProject do
|
|||
{:jason, "~> 1.2"},
|
||||
{:dns_cluster, "~> 0.1.1"},
|
||||
{:bandit, "~> 1.5"},
|
||||
{:oban, "~> 2.17"},
|
||||
{:oban, "~> 2.19"},
|
||||
{:oban_web, "~> 2.11"},
|
||||
{:mox, "~> 0.5.0", only: :test},
|
||||
{:httpoison, "~> 2.0"},
|
||||
{:ueberauth, "~> 0.7.0"},
|
||||
|
@ -63,7 +64,18 @@ defmodule Bright.MixProject do
|
|||
{:ex_aws_s3, "~> 2.0"},
|
||||
{:ex_aws, "~> 2.1"},
|
||||
{:ffmpex, "~> 0.11.0"},
|
||||
{:sweet_xml, "~> 0.6"}
|
||||
{:sweet_xml, "~> 0.6"},
|
||||
{:ex_m3u8, "~> 0.14.2"},
|
||||
# {:membrane_core, "~> 1.0"},
|
||||
# {:membrane_mpeg_ts_plugin, "~> 1.0.3"},
|
||||
# {:membrane_file_plugin, "~> 0.17.2"},
|
||||
# {:membrane_mp4_plugin, "~> 0.35.2"},
|
||||
# {:membrane_http_adaptive_stream_plugin, "> 0.0.0"},
|
||||
# {:membrane_h264_ffmpeg_plugin, "~> 0.32.5"},
|
||||
# {:membrane_aac_plugin, "~> 0.11.0"},
|
||||
# {:membrane_hackney_plugin, "~> 0.6.0"}, # incompatible with membrane_core 1.1.2
|
||||
# {:membrane_mpegts_plugin, "~> 0.4.0"}, # official module is 4 years outdated
|
||||
# {:membrane_mpegts_plugin, path: "/home/cj/Documents/membrane_mpegts_plugin"},
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
%{
|
||||
"bandit": {:hex, :bandit, "1.6.1", "9e01b93d72ddc21d8c576a704949e86ee6cde7d11270a1d3073787876527a48f", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5a904bf010ea24b67979835e0507688e31ac873d4ffc8ed0e5413e8d77455031"},
|
||||
"bandit": {:hex, :bandit, "1.6.6", "f2019a95261d400579075df5bc15641ba8e446cc4777ede6b4ec19e434c3340d", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "ceb19bf154bc2c07ee0c9addf407d817c48107e36a66351500846fc325451bf9"},
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "3.2.0", "feab711974beba4cb348147170346fe097eea2e840db4e012a145e180ed4ab75", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "563e92a6c77d667b19c5f4ba17ab6d440a085696bdf4c68b9b0f5b30bc5422b8"},
|
||||
"bimap": {:hex, :bimap, "1.3.0", "3ea4832e58dc83a9b5b407c6731e7bae87458aa618e6d11d8e12114a17afa4b3", [:mix], [], "hexpm", "bf5a2b078528465aa705f405a5c638becd63e41d280ada41e0f77e6d255a10b4"},
|
||||
"bulma": {:hex, :bulma, "1.0.2", "50dfffe8d28b0bd527418560223b407f9e80e990e187e1653b17eff818f8fcbe", [:mix], [], "hexpm", "27745727ff7f451d140a2438c0ca4448bc8ca73e0a6d2d4f24e1b5b9ced8a774"},
|
||||
"castore": {:hex, :castore, "1.0.10", "43bbeeac820f16c89f79721af1b3e092399b3a1ecc8df1a472738fd853574911", [:mix], [], "hexpm", "1b0b7ea14d889d9ea21202c43a4fa015eb913021cb535e8ed91946f4b77a8848"},
|
||||
"bunch": {:hex, :bunch, "1.6.1", "5393d827a64d5f846092703441ea50e65bc09f37fd8e320878f13e63d410aec7", [:mix], [], "hexpm", "286cc3add551628b30605efbe2fca4e38cc1bea89bcd0a1a7226920b3364fe4a"},
|
||||
"bunch_native": {:hex, :bunch_native, "0.5.0", "8ac1536789a597599c10b652e0b526d8833348c19e4739a0759a2bedfd924e63", [:mix], [{:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "24190c760e32b23b36edeb2dc4852515c7c5b3b8675b1a864e0715bdd1c8f80d"},
|
||||
"bundlex": {:hex, :bundlex, "1.5.4", "3726acd463f4d31894a59bbc177c17f3b574634a524212f13469f41c4834a1d9", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:elixir_uuid, "~> 1.2", [hex: :elixir_uuid, repo: "hexpm", optional: false]}, {:qex, "~> 0.5", [hex: :qex, repo: "hexpm", optional: false]}, {:req, ">= 0.4.0", [hex: :req, repo: "hexpm", optional: false]}, {:zarex, "~> 1.0", [hex: :zarex, repo: "hexpm", optional: false]}], "hexpm", "e745726606a560275182a8ac1c8ebd5e11a659bb7460d8abf30f397e59b4c5d2"},
|
||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||
"castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"coerce": {:hex, :coerce, "1.0.1", "211c27386315dc2894ac11bc1f413a0e38505d808153367bd5c6e75a4003d096", [:mix], [], "hexpm", "b44a691700f7a1a15b4b7e2ff1fa30bebd669929ac8aa43cffe9e2f8bf051cf1"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
|
||||
"crc": {:hex, :crc, "0.10.5", "ee12a7c056ac498ef2ea985ecdc9fa53c1bfb4e53a484d9f17ff94803707dfd8", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3e673b6495a9525c5c641585af1accba59a1eb33de697bedf341e247012c2c7f"},
|
||||
"credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"},
|
||||
"dart_sass": {:hex, :dart_sass, "0.7.0", "7979e056cb74fd6843e1c72db763cffc7726a9192a657735b7d24c0d9c26a1ce", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4a8e70bca41aa00846398abdf5ad8a64d7907a0f7bf40145cd2e40d5971629f2"},
|
||||
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
||||
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
|
||||
|
@ -13,59 +21,98 @@
|
|||
"ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [: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", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 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", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
|
||||
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
|
||||
"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.5.8", "0393cfbc5e4a9e7017845451a015d836a670397100aa4c86901980e2a2c5f7d4", [: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]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {: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", "8f79777b7932168956c8cc3a6db41f5783aa816eb50de356aed3165a71e5f8c3"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.6", "d135983bbd8b6df6350dfd83999437725527c1bea151e5055760bfc9b2d17c20", [: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", "9874e12847e469ca2f13a5689be04e546c16f63caf6380870b7f25bf7cb98875"},
|
||||
"ex_m3u8": {:hex, :ex_m3u8, "0.14.2", "3eb17f936e2ca2fdcde11664f3a543e75a94814d928098e050bda5b1e149c021", [:mix], [{:nimble_parsec, "~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "d2a1fb4382a521cce7f966502ecce6187f286ca2852dbb0dcc25dea72f8ba039"},
|
||||
"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},
|
||||
"ffmpex": {:hex, :ffmpex, "0.11.0", "70d2e211a70e1d8cc1a81d73208d5efedda59d82db4c91160c79e5461529d291", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:rambo, "~> 0.3.0", [hex: :rambo, repo: "hexpm", optional: false]}], "hexpm", "2429d67badc91957ace572b9169615619740904a58791289ba54d99e57a164eb"},
|
||||
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
||||
"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
|
||||
"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},
|
||||
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
|
||||
"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},
|
||||
"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"},
|
||||
"heap": {:hex, :heap, "2.0.2", "d98cb178286cfeb5edbcf17785e2d20af73ca57b5a2cf4af584118afbcf917eb", [:mix], [], "hexpm", "ba9ea2fe99eb4bcbd9a8a28eaf71cbcac449ca1d8e71731596aace9028c9d429"},
|
||||
"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},
|
||||
"hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"},
|
||||
"httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"},
|
||||
"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.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
"logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"},
|
||||
"membrane_aac_format": {:hex, :membrane_aac_format, "0.8.0", "515631eabd6e584e0e9af2cea80471fee6246484dbbefc4726c1d93ece8e0838", [:mix], [{:bimap, "~> 1.1", [hex: :bimap, repo: "hexpm", optional: false]}], "hexpm", "a30176a94491033ed32be45e51d509fc70a5ee6e751f12fd6c0d60bd637013f6"},
|
||||
"membrane_aac_plugin": {:hex, :membrane_aac_plugin, "0.11.1", "9513c87612d6d07fb6878c57fe9b31561c531981026de66517914ffc5a363d77", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:crc, "~> 0.10.2", [hex: :crc, repo: "hexpm", optional: false]}, {:credo, "~> 1.5", [hex: :credo, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.6.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.8.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "0a61139d5422ffd865c70f8aea610f35769770de31753ee7a5925919e4c20d90"},
|
||||
"membrane_caps_video_raw": {:hex, :membrane_caps_video_raw, "0.1.0", "6aa751b0c338ea6672540b7ec7ad2be0d23bad931b8a8776757da9b279070a3b", [:mix], [], "hexpm", "3f60d65189bd9e3b0ab77e0ebf2e0c1b04d0fd6f67c546fc1d54d9958c362ce4"},
|
||||
"membrane_cmaf_format": {:hex, :membrane_cmaf_format, "0.7.1", "9ea858faefdcb181cdfa8001be827c35c5f854e9809ad57d7062cff1f0f703fd", [:mix], [], "hexpm", "3c7b4ed2a986e27f6f336d2f19e9442cb31d93b3142fc024c019572faca54a73"},
|
||||
"membrane_common_c": {:hex, :membrane_common_c, "0.16.0", "caf3f29d2f5a1d32d8c2c122866110775866db2726e4272be58e66dfdf4bce40", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}, {:unifex, "~> 1.0", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "a3c7e91de1ce1f8b23b9823188a5d13654d317235ea0ca781c05353ed3be9b1c"},
|
||||
"membrane_core": {:hex, :membrane_core, "1.1.2", "3ca206893e1d3739a24d5092d21c06fcb4db326733a1798f9788fc53abb74829", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:qex, "~> 0.3", [hex: :qex, repo: "hexpm", optional: false]}, {:ratio, "~> 3.0 or ~> 4.0", [hex: :ratio, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a989fd7e0516a7e66f5fb63950b1027315b7f8c8d82d8d685e178b0fb780901b"},
|
||||
"membrane_file_plugin": {:hex, :membrane_file_plugin, "0.17.2", "650e134c2345d946f930082fac8bac9f5aba785a7817d38a9a9da41ffc56fa92", [:mix], [{:logger_backends, "~> 1.0", [hex: :logger_backends, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "df50c6040004cd7b901cf057bd7e99c875bbbd6ae574efc93b2c753c96f43b9d"},
|
||||
"membrane_h264_ffmpeg_plugin": {:hex, :membrane_h264_ffmpeg_plugin, "0.32.5", "30542fb5d6d36961a51906549b4338f4fc66a304bf92e7c7123e2b9971e3502d", [:mix], [{:bunch, "~> 1.6", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.3", [hex: :bundlex, repo: "hexpm", optional: false]}, {:membrane_common_c, "~> 0.16.0", [hex: :membrane_common_c, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_precompiled_dependency_provider, "~> 0.1.0", [hex: :membrane_precompiled_dependency_provider, repo: "hexpm", optional: false]}, {:membrane_raw_video_format, "~> 0.4.1", [hex: :membrane_raw_video_format, repo: "hexpm", optional: false]}, {:unifex, "~> 1.1", [hex: :unifex, repo: "hexpm", optional: false]}], "hexpm", "8c80e11b9ec9ca23d44304ed7bb3daf665e98b91b2488608ee5718a88182e363"},
|
||||
"membrane_h264_format": {:hex, :membrane_h264_format, "0.6.1", "44836cd9de0abe989b146df1e114507787efc0cf0da2368f17a10c47b4e0738c", [:mix], [], "hexpm", "4b79be56465a876d2eac2c3af99e115374bbdc03eb1dea4f696ee9a8033cd4b0"},
|
||||
"membrane_h265_format": {:hex, :membrane_h265_format, "0.2.0", "1903c072cf7b0980c4d0c117ab61a2cd33e88782b696290de29570a7fab34819", [:mix], [], "hexpm", "6df418bdf242c0d9f7dbf2e5aea4c2d182e34ac9ad5a8b8cef2610c290002e83"},
|
||||
"membrane_hackney_plugin": {:hex, :membrane_hackney_plugin, "0.6.0", "f495da8f8d3b55035d2f38a58b18d16549df9453b2e88517def98a6414a34655", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.8.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:mockery, "~> 2.3", [hex: :mockery, repo: "hexpm", optional: false]}], "hexpm", "16beedf5f829b5ba7aa6850882cbd209b1ff5be2dd84ef675c2a31f48837962f"},
|
||||
"membrane_http_adaptive_stream_plugin": {:hex, :membrane_http_adaptive_stream_plugin, "0.5.0", "9c9b633d0aa12226676e5307735fd9fc56d9e4909054f2bf6d4d4ecf6a62595e", [:mix], [{:credo, "~> 1.6.1", [hex: :credo, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.5.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.8.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_mp4_plugin, "~> 0.11.0", [hex: :membrane_mp4_plugin, repo: "hexpm", optional: false]}, {:membrane_tee_plugin, "~> 0.7.0", [hex: :membrane_tee_plugin, repo: "hexpm", optional: false]}], "hexpm", "e00cbbfb9bb2cb2a9d0abf80cc1aa8a245577278d793b4b980951479713a7684"},
|
||||
"membrane_mp4_format": {:hex, :membrane_mp4_format, "0.8.0", "8c6e7d68829228117d333b4fbb030e7be829aab49dd8cb047fdc664db1812e6a", [:mix], [], "hexpm", "148dea678a1f82ccfd44dbde6f936d2f21255f496cb45a22cc6eec427f025522"},
|
||||
"membrane_mp4_plugin": {:hex, :membrane_mp4_plugin, "0.35.2", "cbedb5272ef1c8f7d9cd3c44f820a90306469b1dc84b8db30ff55bb6195b7cb2", [:mix], [{:bunch, "~> 1.5", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_aac_format, "~> 0.8.0", [hex: :membrane_aac_format, repo: "hexpm", optional: false]}, {:membrane_cmaf_format, "~> 0.7.0", [hex: :membrane_cmaf_format, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_file_plugin, "~> 0.17.0", [hex: :membrane_file_plugin, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:membrane_h265_format, "~> 0.2.0", [hex: :membrane_h265_format, repo: "hexpm", optional: false]}, {:membrane_mp4_format, "~> 0.8.0", [hex: :membrane_mp4_format, repo: "hexpm", optional: false]}, {:membrane_opus_format, "~> 0.3.0", [hex: :membrane_opus_format, repo: "hexpm", optional: false]}, {:membrane_timestamp_queue, "~> 0.2.1", [hex: :membrane_timestamp_queue, repo: "hexpm", optional: false]}], "hexpm", "8afd4e7779a742dd56c23f1f23053933d1b0b34d397ad368a2f56f995edb2fe0"},
|
||||
"membrane_mpeg_ts_plugin": {:hex, :membrane_mpeg_ts_plugin, "1.0.3", "6ca4edeee4d80d936214ed90be4bb43fc9cf75b2391a962182d3e277b517de69", [:mix], [{:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}, {:membrane_h264_format, "~> 0.6.1", [hex: :membrane_h264_format, repo: "hexpm", optional: false]}, {:mpeg_ts, "~> 1.0", [hex: :mpeg_ts, repo: "hexpm", optional: false]}], "hexpm", "90d8eae64b02e54924d8505653a57c91b9a72211f34a770b7f9ded25baf0fcc9"},
|
||||
"membrane_mpegts_plugin": {:hex, :membrane_mpegts_plugin, "0.4.0", "e055da53a7a54cc42e280da229e4ff6c9257103400524ebfe8b33502841c14f5", [:mix], [{:membrane_core, "~> 0.8.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "4bb6e8a4d265147acd95a4672930233345807923c7f19280ca2293050ef961b8"},
|
||||
"membrane_opus_format": {:hex, :membrane_opus_format, "0.3.0", "3804d9916058b7cfa2baa0131a644d8186198d64f52d592ae09e0942513cb4c2", [:mix], [], "hexpm", "8fc89c97be50de23ded15f2050fe603dcce732566fe6fdd15a2de01cb6b81afe"},
|
||||
"membrane_precompiled_dependency_provider": {:hex, :membrane_precompiled_dependency_provider, "0.1.2", "8af73b7dc15ba55c9f5fbfc0453d4a8edfb007ade54b56c37d626be0d1189aba", [:mix], [{:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "7fe3e07361510445a29bee95336adde667c4162b76b7f4c8af3aeb3415292023"},
|
||||
"membrane_raw_video_format": {:hex, :membrane_raw_video_format, "0.4.1", "d7344499c2d80f236a7ef962b5490c651341a501052ee43dec56cf0319fa3936", [:mix], [], "hexpm", "9920b7d445b5357608a364fec5685acdfce85334c647f745045237a0d296c442"},
|
||||
"membrane_tee_plugin": {:hex, :membrane_tee_plugin, "0.7.0", "b4705938a388fba8ce973dbdce8a5e95c963c0370bcc48311c0a40c2ea5b0ad8", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:membrane_core, "~> 0.8.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "f029fb95ca4e2178559629691715a042252fd756914c5929cc054690205f391d"},
|
||||
"membrane_timestamp_queue": {:hex, :membrane_timestamp_queue, "0.2.2", "1c831b2273d018a6548654aa9f7fa7c4b683f71d96ffe164934ef55f9d11f693", [:mix], [{:heap, "~> 2.0", [hex: :heap, repo: "hexpm", optional: false]}, {:membrane_core, "~> 1.0", [hex: :membrane_core, repo: "hexpm", optional: false]}], "hexpm", "7c830e760baaced0988421671cd2c83c7cda8d1bd2b61fd05332711675d1204f"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
|
||||
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
|
||||
"mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"},
|
||||
"mockery": {:hex, :mockery, "2.3.3", "3dba87bd0422a513e6af6e0d811383f38f82ac6be5d3d285a5fcca9c299bd0ac", [:mix], [], "hexpm", "17282be00613286254298117cd25e607a39f15ac03b41c631f60e52f5b5ec974"},
|
||||
"mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"},
|
||||
"mpeg_ts": {:hex, :mpeg_ts, "1.0.2", "dc548ea9de58df93c2e9ddd006a5f4523c29d0ecfeb1189bb87ed4c458f6b2a2", [:mix], [], "hexpm", "eaa3c179670f4bf326ff974d13845aac3107bfe42f894c8ed33130d89a818f67"},
|
||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||
"numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"},
|
||||
"oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"},
|
||||
"oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"},
|
||||
"oban": {:hex, :oban, "2.19.0", "dfb8fa028ce7e7cf3be3481a47a7c8ebf9428d6df0aa58c1388a8e63f7ff2797", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa3eb7cfa2aea8ecc4df4787b92ddb61ad5a598f07560937d1dd5dbb1ed225e2"},
|
||||
"oban_met": {:hex, :oban_met, "1.0.1", "737db0064567b923d3f35efd1d3009dd1435d60ee6f98dbb55dbb83db8f4f4fa", [:mix], [{:oban, "~> 2.18", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "0492d841f880b76c5b73081bc70ebea20ebacc08e871345f72c2270513f09957"},
|
||||
"oban_web": {:hex, :oban_web, "2.11.0", "8b2a23331ef7e60eabb4118a141880d89812820321b21f289f1696bcf3058810", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "a573f27bf7cb054ff2a694116428dc6fedc18e20a20d10a74934b7c9e473e562"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||
"phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [: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", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.5", "d5f44d7dbd7cfacaa617b70c5a14b2b598d6f93b9caa8e350c51d56cd4350a9b", [: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", "1d73920515554d7d6c548aee0bf10a4780568b029d042eccb336db29ea0dad70"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [: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", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.1", "5389a30658176c0de816636ce276567478bffd063c082515a6e8368b8fc9a0db", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c0f517e6f290f10dbb94343ac22e0109437fb1fa6f0696e7c73967b789c1c285"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {: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 or ~> 4.0", [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]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"},
|
||||
"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.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [: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", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"},
|
||||
"qex": {:hex, :qex, "0.5.1", "0d82c0f008551d24fffb99d97f8299afcb8ea9cf99582b770bd004ed5af63fd6", [:mix], [], "hexpm", "935a39fdaf2445834b95951456559e9dc2063d0a055742c558a99987b38d6bab"},
|
||||
"rambo": {:hex, :rambo, "0.3.4", "8962ac3bd1a633ee9d0e8b44373c7913e3ce3d875b4151dcd060886092d2dce7", [:mix], [], "hexpm", "0cc54ed089fbbc84b65f4b8a774224ebfe60e5c80186fafc7910b3e379ad58f1"},
|
||||
"ratio": {:hex, :ratio, "4.0.1", "3044166f2fc6890aa53d3aef0c336f84b2bebb889dc57d5f95cc540daa1912f8", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:numbers, "~> 5.2.0", [hex: :numbers, repo: "hexpm", optional: false]}], "hexpm", "c60cbb3ccdff9ffa56e7d6d1654b5c70d9f90f4d753ab3a43a6bf40855b881ce"},
|
||||
"redirect": {:hex, :redirect, "0.4.0", "98b46053504ee517bc3ad2fd04c064b64b48d339e1e18266355b30c4f8bb52b0", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.3 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dfa29a8ecbad066ed0b73b34611cf24c78101719737f37bdf750f39197d67b97"},
|
||||
"req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{: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, "~> 2.0.6 or ~> 2.1", [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", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},
|
||||
"secure_random": {:hex, :secure_random, "0.5.1", "c5532b37c89d175c328f5196a0c2a5680b15ebce3e654da37129a9fe40ebf51b", [:mix], [], "hexpm", "1b9754f15e3940a143baafd19da12293f100044df69ea12db5d72878312ae6ab"},
|
||||
"shmex": {:hex, :shmex, "0.5.1", "81dd209093416bf6608e66882cb7e676089307448a1afd4fc906c1f7e5b94cf4", [:mix], [{:bunch_native, "~> 0.5.0", [hex: :bunch_native, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.0", [hex: :bundlex, repo: "hexpm", optional: false]}], "hexpm", "c29f8286891252f64c4e1dac40b217d960f7d58def597c4e606ff8fbe71ceb80"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||
"superstreamer_player": {:git, "https://github.com/superstreamerapp/superstreamer.git", "9e868acede851f396b3db98fb9799ab4bf712b02", [sparse: "packages/player"]},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
|
||||
"swoosh": {:hex, :swoosh, "1.17.5", "14910d267a2633d4335917b37846e376e2067815601592629366c39845dad145", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "629113d477bc82c4c3bffd15a25e8becc1c7ccc0f0e67743b017caddebb06f04"},
|
||||
"swoosh": {:hex, :swoosh, "1.17.6", "27ff070f96246e35b7105ab1c52b2b689f523a3cb83ed9faadb2f33bd653ccba", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {: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]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9798f3e72165f40c950f6762c06dab68afcdcf616138fc4a07965c09c250e1e2"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"},
|
||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "1.0.0", "29f5f84991ca98b8eb02fc208b2e6de7c95f8bb2294ef244a176675adc7775df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f23713b3847286a534e005126d4c959ebcca68ae9582118ce436b521d1d47d5d"},
|
||||
"telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"},
|
||||
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
||||
"tesla": {:hex, :tesla, "1.12.3", "7189f71ac607169a1bb2dfcf8747dedd4d9384ec00cec6c7b38c5f03811a73c7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "4dfb0d6a81ca79c8662a4f03884843a5b3251825ba47ea6f9ab84dcc354fdeec"},
|
||||
"thousand_island": {:hex, :thousand_island, "1.3.7", "1da7598c0f4f5f50562c097a3f8af308ded48cd35139f0e6f17d9443e4d0c9c5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0139335079953de41d381a6134d8b618d53d084f558c734f2662d1a72818dd12"},
|
||||
"thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"},
|
||||
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
||||
"typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"},
|
||||
"tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"},
|
||||
"ueberauth": {:hex, :ueberauth, "0.7.0", "9c44f41798b5fa27f872561b6f7d2bb0f10f03fdd22b90f454232d7b087f4b75", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2efad9022e949834f16cc52cd935165049d81fa9e925690f91035c2e4b58d905"},
|
||||
"ueberauth_github": {:hex, :ueberauth_github, "0.8.3", "1c478629b4c1dae446c68834b69194ad5cead3b6c67c913db6fdf64f37f0328f", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "ae0ab2879c32cfa51d7287a48219b262bfdab0b7ec6629f24160564247493cc6"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
"unifex": {:hex, :unifex, "1.2.1", "6841c170a6e16509fac30b19e4e0a19937c33155a59088b50c15fc2c36251b6b", [:mix], [{:bunch, "~> 1.0", [hex: :bunch, repo: "hexpm", optional: false]}, {:bundlex, "~> 1.4", [hex: :bundlex, repo: "hexpm", optional: false]}, {:shmex, "~> 0.5.0", [hex: :shmex, repo: "hexpm", optional: false]}], "hexpm", "8c9d2e3c48df031e9995dd16865bab3df402c0295ba3a31f38274bb5314c7d37"},
|
||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [: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", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
|
||||
"zarex": {:hex, :zarex, "1.0.5", "58239e3ee5d75f343262bb4df5cf466555a1c689f920e5d3651a9333972f7c7e", [:mix], [], "hexpm", "9fb72ef0567c2b2742f5119a1ba8a24a2fabb21b8d09820aefbf3e592fa9a46a"},
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
defmodule Bright.Repo.Migrations.RemoveAuthAndUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
drop_if_exists table(:users_tokens), cascade: true
|
||||
drop_if_exists table(:users), cascade: true
|
||||
drop_if_exists table(:cart_items), cascade: true
|
||||
drop_if_exists table(:carts), cascade: true
|
||||
drop_if_exists table(:product_categories), cascade: true
|
||||
drop_if_exists table(:categories), cascade: true
|
||||
drop_if_exists table(:order_line_items), cascade: true
|
||||
drop_if_exists table(:orders), cascade: true
|
||||
drop_if_exists table(:posts), cascade: true
|
||||
drop_if_exists table(:products), cascade: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
defmodule Bright.Repo.Migrations.CreateUsersAuthTables do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
execute "CREATE EXTENSION IF NOT EXISTS citext", ""
|
||||
|
||||
create table(:users) do
|
||||
add :email, :citext, null: false
|
||||
add :hashed_password, :string, null: false
|
||||
add :confirmed_at, :utc_datetime
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create unique_index(:users, [:email])
|
||||
|
||||
create table(:users_tokens) do
|
||||
add :user_id, references(:users, on_delete: :delete_all), null: false
|
||||
add :token, :binary, null: false
|
||||
add :context, :string, null: false
|
||||
add :sent_to, :string
|
||||
|
||||
timestamps(type: :utc_datetime, updated_at: false)
|
||||
end
|
||||
|
||||
create index(:users_tokens, [:user_id])
|
||||
create unique_index(:users_tokens, [:context, :token])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Bright.Repo.Migrations.AddUploadedByToVods do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:vods) do
|
||||
add :uploaded_by_id, references(:users, on_delete: :nothing)
|
||||
end
|
||||
|
||||
create index(:vods, [:uploaded_by_id])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Bright.Repo.Migrations.AddGithubId do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :github_id, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule Bright.Repo.Migrations.AddUserAvatarName do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :avatar, :string
|
||||
add :name, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Bright.Repo.Migrations.RemoveEmailAndPassword do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
remove :email
|
||||
remove :hashed_password
|
||||
remove :confirmed_at
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Bright.Repo.Migrations.AddLocalPath do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:vods) do
|
||||
add :local_path, :string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Bright.Repo.Migrations.AddDuration do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:vods) do
|
||||
add :duration, :integer
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
HELLO MAMA
|
||||
|
||||
haha
|
|
@ -3,7 +3,8 @@ defmodule Bright.ImagesTest do
|
|||
|
||||
alias Bright.Images
|
||||
|
||||
@test_fixture "./test/fixtures/SampleVideo_1280x720_1mb.mp4"
|
||||
@test_mp4_fixture "./test/fixtures/SampleVideo_1280x720_1mb.mp4"
|
||||
@test_ts_fixture "./test/fixtures/test-fixture.ts"
|
||||
|
||||
describe "thumbnails" do
|
||||
|
||||
|
@ -11,7 +12,7 @@ defmodule Bright.ImagesTest do
|
|||
|
||||
@tag :unit
|
||||
test "create_thumbnail/1" do
|
||||
{:ok, %{:output => output, :filename => filename}} = Images.create_thumbnail(@test_fixture)
|
||||
{:ok, %{:output => output, :filename => filename}} = Images.create_thumbnail(@test_mp4_fixture)
|
||||
assert output === ""
|
||||
assert Regex.match?(~r/[a-zA-Z0-9]+-.*\.png$/, filename)
|
||||
assert File.exists?(filename)
|
||||
|
@ -25,9 +26,9 @@ defmodule Bright.ImagesTest do
|
|||
basename = "thumb.jpg"
|
||||
random_string = for _ <- 1..12, into: "", do: <<Enum.random(~c"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")>>
|
||||
output_file = "/tmp/#{random_string}-#{basename}"
|
||||
IO.puts "output_file=#{inspect(output_file)} @test_fixture=#{inspect(@test_fixture)}"
|
||||
IO.puts "output_file=#{inspect(output_file)} @test_mp4_fixture=#{inspect(@test_mp4_fixture)}"
|
||||
|
||||
{:ok, output } = Images.create_thumbnail(@test_fixture, output_file)
|
||||
{:ok, output } = Images.create_thumbnail(@test_mp4_fixture, output_file)
|
||||
|
||||
assert File.exists?(output_file)
|
||||
{:ok, stat} = File.stat(output_file)
|
||||
|
@ -55,17 +56,23 @@ defmodule Bright.ImagesTest do
|
|||
describe "get_video_duration" do
|
||||
@tag :integration
|
||||
test "should get video stream duration" do
|
||||
{:ok, duration} = Images.get_video_duration(@test_fixture)
|
||||
{:ok, duration} = Images.get_video_duration(@test_mp4_fixture)
|
||||
assert duration === "5.280000"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_video_framecount" do
|
||||
@tag :integration
|
||||
test "should get video frame count" do
|
||||
{:ok, nb_frames} = Images.get_video_framecount(@test_fixture)
|
||||
test "should get video frame count from a mp4 which contains framecount in metadata" do
|
||||
{:ok, nb_frames} = Images.get_video_framecount(@test_mp4_fixture)
|
||||
assert nb_frames === 132
|
||||
end
|
||||
|
||||
@tag :integration
|
||||
test "should get video frame count from a ts which does not contain framecount in metadata" do
|
||||
{:ok, nb_read_frames} = Images.get_video_framecount(@test_ts_fixture)
|
||||
assert nb_read_frames === 99
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -54,6 +54,20 @@ defmodule Bright.ObanWorkers.CreateThumbnailTest do
|
|||
refute_enqueued worker: CreateThumbnail
|
||||
end
|
||||
|
||||
@tag :integration
|
||||
test "not scheduled when playlist_url is missing" do
|
||||
# we do this because .ts files dont usually have nb_frames in stream metadata, which we require to generate a thumbnail.
|
||||
# we wait until we have processsed the hls playlist to create the thumbnail.
|
||||
|
||||
stream_attrs = %{date: ~U[2024-12-28 03:31:00Z], title: "some title", notes: "some notes"}
|
||||
{:ok, %Stream{} = stream} = Streams.create_stream(stream_attrs)
|
||||
{:ok, _vod} = Streams.create_vod(%{stream_id: stream.id})
|
||||
|
||||
refute_enqueued worker: CreateThumbnail
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -66,7 +66,7 @@ defmodule Bright.StreamsTest do
|
|||
|
||||
import Bright.StreamsFixtures
|
||||
|
||||
@invalid_attrs %{stream_id: nil, s3_cdn_url: nil, s3_upload_id: nil, s3_key: nil, s3_bucket: nil, mux_asset_id: nil, mux_playback_id: nil, ipfs_cid: nil, torrent: nil}
|
||||
@invalid_attrs %{stream_id: nil, s3_cdn_url: nil, s3_key: nil, s3_bucket: nil, mux_asset_id: nil, mux_playback_id: nil, ipfs_cid: nil, torrent: nil}
|
||||
|
||||
test "list_vods/0 returns all vods" do
|
||||
stream = stream_fixture()
|
||||
|
@ -82,15 +82,10 @@ defmodule Bright.StreamsTest do
|
|||
|
||||
test "create_vod/1 with valid data creates a vod" do
|
||||
stream = stream_fixture()
|
||||
valid_attrs = %{stream_id: stream.id, s3_cdn_url: "some s3_cdn_url", s3_upload_id: "some s3_upload_id", s3_key: "some s3_key", s3_bucket: "some s3_bucket", mux_asset_id: "some mux_asset_id", mux_playback_id: "some mux_playback_id", ipfs_cid: "some ipfs_cid", torrent: "some torrent"}
|
||||
valid_attrs = %{stream_id: stream.id, s3_cdn_url: "some s3_cdn_url", s3_key: "some s3_key", s3_bucket: "some s3_bucket", mux_asset_id: "some mux_asset_id", mux_playback_id: "some mux_playback_id", ipfs_cid: "some ipfs_cid", torrent: "some torrent"}
|
||||
|
||||
assert {:ok, %Vod{} = vod} = Streams.create_vod(valid_attrs)
|
||||
assert vod.s3_cdn_url == "some s3_cdn_url"
|
||||
assert vod.s3_upload_id == "some s3_upload_id"
|
||||
assert vod.s3_key == "some s3_key"
|
||||
assert vod.s3_bucket == "some s3_bucket"
|
||||
assert vod.mux_asset_id == "some mux_asset_id"
|
||||
assert vod.mux_playback_id == "some mux_playback_id"
|
||||
assert vod.ipfs_cid == "some ipfs_cid"
|
||||
assert vod.torrent == "some torrent"
|
||||
end
|
||||
|
@ -106,9 +101,6 @@ defmodule Bright.StreamsTest do
|
|||
|
||||
assert {:ok, %Vod{} = vod} = Streams.update_vod(vod, update_attrs)
|
||||
assert vod.s3_cdn_url == "some updated s3_cdn_url"
|
||||
assert vod.s3_upload_id == "some updated s3_upload_id"
|
||||
assert vod.s3_key == "some updated s3_key"
|
||||
assert vod.s3_bucket == "some updated s3_bucket"
|
||||
assert vod.mux_asset_id == "some updated mux_asset_id"
|
||||
assert vod.mux_playback_id == "some updated mux_playback_id"
|
||||
assert vod.ipfs_cid == "some updated ipfs_cid"
|
||||
|
@ -133,6 +125,39 @@ defmodule Bright.StreamsTest do
|
|||
stream = stream_fixture()
|
||||
vod = vod_fixture(%{stream_id: stream.id})
|
||||
assert %Ecto.Changeset{} = Streams.change_vod(vod)
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "processing" do
|
||||
|
||||
alias Bright.Streams
|
||||
|
||||
import Bright.StreamsFixtures
|
||||
|
||||
# test "get_duration/1" do
|
||||
# playlist_url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8"
|
||||
# stream = stream_fixture()
|
||||
# vod = vod_fixture(%{playlist_url: playlist_url, stream_id: stream.id})
|
||||
# {:ok, duration } = Bright.Streams.get_duration(vod)
|
||||
# assert :ok
|
||||
# assert duration == 3
|
||||
# end
|
||||
|
||||
test "transmux_to_hls/2" do
|
||||
stream = stream_fixture()
|
||||
vod = vod_fixture(%{stream_id: stream.id, playlist_url: nil, origin_temp_input_url: "https://futureporn-b2.b-cdn.net/test-fixture.ts"})
|
||||
|
||||
callback = fn progress -> send(self(), {:progress, progress}) end
|
||||
{:ok, updated_vod} = Streams.transmux_to_hls(vod, callback)
|
||||
|
||||
assert :ok
|
||||
assert updated_vod.local_path != nil
|
||||
assert_received {:progress, %{stage: :transmuxing, done: 1, total: 1}}
|
||||
assert_received {:progress, %{stage: :persisting, done: 1, total: _}}
|
||||
# assert_received {:progress, %{stage: :generating_thumbnail, done: 1, total: 1}}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
defmodule BrightWeb.PostLiveTest do
|
||||
use BrightWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Bright.BlogFixtures
|
||||
|
||||
@create_attrs %{title: "some title", body: "some body"}
|
||||
@update_attrs %{title: "some updated title", body: "some updated body"}
|
||||
@invalid_attrs %{title: nil, body: nil}
|
||||
|
||||
defp create_post(_) do
|
||||
post = post_fixture()
|
||||
%{post: post}
|
||||
end
|
||||
|
||||
describe "Index" do
|
||||
setup [:create_post]
|
||||
|
||||
test "lists all posts", %{conn: conn, post: post} do
|
||||
{:ok, _index_live, html} = live(conn, ~p"/posts")
|
||||
|
||||
assert html =~ "Listing Posts"
|
||||
assert html =~ post.title
|
||||
end
|
||||
|
||||
test "saves new post", %{conn: conn} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/posts")
|
||||
|
||||
assert index_live |> element("a", "New Post") |> render_click() =~
|
||||
"New Post"
|
||||
|
||||
assert_patch(index_live, ~p"/posts/new")
|
||||
|
||||
assert index_live
|
||||
|> form("#post-form", post: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#post-form", post: @create_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/posts")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Post created successfully"
|
||||
assert html =~ "some title"
|
||||
end
|
||||
|
||||
test "updates post in listing", %{conn: conn, post: post} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/posts")
|
||||
|
||||
assert index_live |> element("#posts-#{post.id} a", "Edit") |> render_click() =~
|
||||
"Edit Post"
|
||||
|
||||
assert_patch(index_live, ~p"/posts/#{post}/edit")
|
||||
|
||||
assert index_live
|
||||
|> form("#post-form", post: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert index_live
|
||||
|> form("#post-form", post: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(index_live, ~p"/posts")
|
||||
|
||||
html = render(index_live)
|
||||
assert html =~ "Post updated successfully"
|
||||
assert html =~ "some updated title"
|
||||
end
|
||||
|
||||
test "deletes post in listing", %{conn: conn, post: post} do
|
||||
{:ok, index_live, _html} = live(conn, ~p"/posts")
|
||||
|
||||
assert index_live |> element("#posts-#{post.id} a", "Delete") |> render_click()
|
||||
refute has_element?(index_live, "#posts-#{post.id}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "Show" do
|
||||
setup [:create_post]
|
||||
|
||||
test "displays post", %{conn: conn, post: post} do
|
||||
{:ok, _show_live, html} = live(conn, ~p"/posts/#{post}")
|
||||
|
||||
assert html =~ "Show Post"
|
||||
assert html =~ post.title
|
||||
end
|
||||
|
||||
test "updates post within modal", %{conn: conn, post: post} do
|
||||
{:ok, show_live, _html} = live(conn, ~p"/posts/#{post}")
|
||||
|
||||
assert show_live |> element("a", "Edit") |> render_click() =~
|
||||
"Edit Post"
|
||||
|
||||
assert_patch(show_live, ~p"/posts/#{post}/show/edit")
|
||||
|
||||
assert show_live
|
||||
|> form("#post-form", post: @invalid_attrs)
|
||||
|> render_change() =~ "can't be blank"
|
||||
|
||||
assert show_live
|
||||
|> form("#post-form", post: @update_attrs)
|
||||
|> render_submit()
|
||||
|
||||
assert_patch(show_live, ~p"/posts/#{post}")
|
||||
|
||||
html = render(show_live)
|
||||
assert html =~ "Post updated successfully"
|
||||
assert html =~ "some updated title"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,272 +0,0 @@
|
|||
defmodule BrightWeb.UserAuthTest do
|
||||
use BrightWeb.ConnCase, async: true
|
||||
|
||||
alias Phoenix.LiveView
|
||||
alias Bright.Accounts
|
||||
alias BrightWeb.UserAuth
|
||||
import Bright.AccountsFixtures
|
||||
|
||||
@remember_me_cookie "_bright_web_user_remember_me"
|
||||
|
||||
setup %{conn: conn} do
|
||||
conn =
|
||||
conn
|
||||
|> Map.replace!(:secret_key_base, BrightWeb.Endpoint.config(:secret_key_base))
|
||||
|> init_test_session(%{})
|
||||
|
||||
%{user: user_fixture(), conn: conn}
|
||||
end
|
||||
|
||||
describe "log_in_user/3" do
|
||||
test "stores the user token in the session", %{conn: conn, user: user} do
|
||||
conn = UserAuth.log_in_user(conn, user)
|
||||
assert token = get_session(conn, :user_token)
|
||||
assert get_session(conn, :live_socket_id) == "users_sessions:#{Base.url_encode64(token)}"
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
assert Accounts.get_user_by_session_token(token)
|
||||
end
|
||||
|
||||
test "clears everything previously stored in the session", %{conn: conn, user: user} do
|
||||
conn = conn |> put_session(:to_be_removed, "value") |> UserAuth.log_in_user(user)
|
||||
refute get_session(conn, :to_be_removed)
|
||||
end
|
||||
|
||||
test "redirects to the configured path", %{conn: conn, user: user} do
|
||||
conn = conn |> put_session(:user_return_to, "/hello") |> UserAuth.log_in_user(user)
|
||||
assert redirected_to(conn) == "/hello"
|
||||
end
|
||||
|
||||
test "writes a cookie if remember_me is configured", %{conn: conn, user: user} do
|
||||
conn = conn |> fetch_cookies() |> UserAuth.log_in_user(user, %{"remember_me" => "true"})
|
||||
assert get_session(conn, :user_token) == conn.cookies[@remember_me_cookie]
|
||||
|
||||
assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]
|
||||
assert signed_token != get_session(conn, :user_token)
|
||||
assert max_age == 5_184_000
|
||||
end
|
||||
end
|
||||
|
||||
describe "logout_user/1" do
|
||||
test "erases session and cookies", %{conn: conn, user: user} do
|
||||
user_token = Accounts.generate_user_session_token(user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_session(:user_token, user_token)
|
||||
|> put_req_cookie(@remember_me_cookie, user_token)
|
||||
|> fetch_cookies()
|
||||
|> UserAuth.log_out_user()
|
||||
|
||||
refute get_session(conn, :user_token)
|
||||
refute conn.cookies[@remember_me_cookie]
|
||||
assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
refute Accounts.get_user_by_session_token(user_token)
|
||||
end
|
||||
|
||||
test "broadcasts to the given live_socket_id", %{conn: conn} do
|
||||
live_socket_id = "users_sessions:abcdef-token"
|
||||
BrightWeb.Endpoint.subscribe(live_socket_id)
|
||||
|
||||
conn
|
||||
|> put_session(:live_socket_id, live_socket_id)
|
||||
|> UserAuth.log_out_user()
|
||||
|
||||
assert_receive %Phoenix.Socket.Broadcast{event: "disconnect", topic: ^live_socket_id}
|
||||
end
|
||||
|
||||
test "works even if user is already logged out", %{conn: conn} do
|
||||
conn = conn |> fetch_cookies() |> UserAuth.log_out_user()
|
||||
refute get_session(conn, :user_token)
|
||||
assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
end
|
||||
end
|
||||
|
||||
describe "fetch_current_user/2" do
|
||||
test "authenticates user from session", %{conn: conn, user: user} do
|
||||
user_token = Accounts.generate_user_session_token(user)
|
||||
conn = conn |> put_session(:user_token, user_token) |> UserAuth.fetch_current_user([])
|
||||
assert conn.assigns.current_user.id == user.id
|
||||
end
|
||||
|
||||
test "authenticates user from cookies", %{conn: conn, user: user} do
|
||||
logged_in_conn =
|
||||
conn |> fetch_cookies() |> UserAuth.log_in_user(user, %{"remember_me" => "true"})
|
||||
|
||||
user_token = logged_in_conn.cookies[@remember_me_cookie]
|
||||
%{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_cookie(@remember_me_cookie, signed_token)
|
||||
|> UserAuth.fetch_current_user([])
|
||||
|
||||
assert conn.assigns.current_user.id == user.id
|
||||
assert get_session(conn, :user_token) == user_token
|
||||
|
||||
assert get_session(conn, :live_socket_id) ==
|
||||
"users_sessions:#{Base.url_encode64(user_token)}"
|
||||
end
|
||||
|
||||
test "does not authenticate if data is missing", %{conn: conn, user: user} do
|
||||
_ = Accounts.generate_user_session_token(user)
|
||||
conn = UserAuth.fetch_current_user(conn, [])
|
||||
refute get_session(conn, :user_token)
|
||||
refute conn.assigns.current_user
|
||||
end
|
||||
end
|
||||
|
||||
describe "on_mount :mount_current_user" do
|
||||
test "assigns current_user based on a valid user_token", %{conn: conn, user: user} do
|
||||
user_token = Accounts.generate_user_session_token(user)
|
||||
session = conn |> put_session(:user_token, user_token) |> get_session()
|
||||
|
||||
{:cont, updated_socket} =
|
||||
UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{})
|
||||
|
||||
assert updated_socket.assigns.current_user.id == user.id
|
||||
end
|
||||
|
||||
test "assigns nil to current_user assign if there isn't a valid user_token", %{conn: conn} do
|
||||
user_token = "invalid_token"
|
||||
session = conn |> put_session(:user_token, user_token) |> get_session()
|
||||
|
||||
{:cont, updated_socket} =
|
||||
UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{})
|
||||
|
||||
assert updated_socket.assigns.current_user == nil
|
||||
end
|
||||
|
||||
test "assigns nil to current_user assign if there isn't a user_token", %{conn: conn} do
|
||||
session = conn |> get_session()
|
||||
|
||||
{:cont, updated_socket} =
|
||||
UserAuth.on_mount(:mount_current_user, %{}, session, %LiveView.Socket{})
|
||||
|
||||
assert updated_socket.assigns.current_user == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "on_mount :ensure_authenticated" do
|
||||
test "authenticates current_user based on a valid user_token", %{conn: conn, user: user} do
|
||||
user_token = Accounts.generate_user_session_token(user)
|
||||
session = conn |> put_session(:user_token, user_token) |> get_session()
|
||||
|
||||
{:cont, updated_socket} =
|
||||
UserAuth.on_mount(:ensure_authenticated, %{}, session, %LiveView.Socket{})
|
||||
|
||||
assert updated_socket.assigns.current_user.id == user.id
|
||||
end
|
||||
|
||||
test "redirects to login page if there isn't a valid user_token", %{conn: conn} do
|
||||
user_token = "invalid_token"
|
||||
session = conn |> put_session(:user_token, user_token) |> get_session()
|
||||
|
||||
socket = %LiveView.Socket{
|
||||
endpoint: BrightWeb.Endpoint,
|
||||
assigns: %{__changed__: %{}, flash: %{}}
|
||||
}
|
||||
|
||||
{:halt, updated_socket} = UserAuth.on_mount(:ensure_authenticated, %{}, session, socket)
|
||||
assert updated_socket.assigns.current_user == nil
|
||||
end
|
||||
|
||||
test "redirects to login page if there isn't a user_token", %{conn: conn} do
|
||||
session = conn |> get_session()
|
||||
|
||||
socket = %LiveView.Socket{
|
||||
endpoint: BrightWeb.Endpoint,
|
||||
assigns: %{__changed__: %{}, flash: %{}}
|
||||
}
|
||||
|
||||
{:halt, updated_socket} = UserAuth.on_mount(:ensure_authenticated, %{}, session, socket)
|
||||
assert updated_socket.assigns.current_user == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "on_mount :redirect_if_user_is_authenticated" do
|
||||
test "redirects if there is an authenticated user ", %{conn: conn, user: user} do
|
||||
user_token = Accounts.generate_user_session_token(user)
|
||||
session = conn |> put_session(:user_token, user_token) |> get_session()
|
||||
|
||||
assert {:halt, _updated_socket} =
|
||||
UserAuth.on_mount(
|
||||
:redirect_if_user_is_authenticated,
|
||||
%{},
|
||||
session,
|
||||
%LiveView.Socket{}
|
||||
)
|
||||
end
|
||||
|
||||
test "doesn't redirect if there is no authenticated user", %{conn: conn} do
|
||||
session = conn |> get_session()
|
||||
|
||||
assert {:cont, _updated_socket} =
|
||||
UserAuth.on_mount(
|
||||
:redirect_if_user_is_authenticated,
|
||||
%{},
|
||||
session,
|
||||
%LiveView.Socket{}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "redirect_if_user_is_authenticated/2" do
|
||||
test "redirects if user is authenticated", %{conn: conn, user: user} do
|
||||
conn = conn |> assign(:current_user, user) |> UserAuth.redirect_if_user_is_authenticated([])
|
||||
assert conn.halted
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
end
|
||||
|
||||
test "does not redirect if user is not authenticated", %{conn: conn} do
|
||||
conn = UserAuth.redirect_if_user_is_authenticated(conn, [])
|
||||
refute conn.halted
|
||||
refute conn.status
|
||||
end
|
||||
end
|
||||
|
||||
describe "require_authenticated_user/2" do
|
||||
test "redirects if user is not authenticated", %{conn: conn} do
|
||||
conn = conn |> fetch_flash() |> UserAuth.require_authenticated_user([])
|
||||
assert conn.halted
|
||||
|
||||
assert redirected_to(conn) == ~p"/users/log_in"
|
||||
|
||||
assert Phoenix.Flash.get(conn.assigns.flash, :error) ==
|
||||
"You must log in to access this page."
|
||||
end
|
||||
|
||||
test "stores the path to redirect to on GET", %{conn: conn} do
|
||||
halted_conn =
|
||||
%{conn | path_info: ["foo"], query_string: ""}
|
||||
|> fetch_flash()
|
||||
|> UserAuth.require_authenticated_user([])
|
||||
|
||||
assert halted_conn.halted
|
||||
assert get_session(halted_conn, :user_return_to) == "/foo"
|
||||
|
||||
halted_conn =
|
||||
%{conn | path_info: ["foo"], query_string: "bar=baz"}
|
||||
|> fetch_flash()
|
||||
|> UserAuth.require_authenticated_user([])
|
||||
|
||||
assert halted_conn.halted
|
||||
assert get_session(halted_conn, :user_return_to) == "/foo?bar=baz"
|
||||
|
||||
halted_conn =
|
||||
%{conn | path_info: ["foo"], query_string: "bar", method: "POST"}
|
||||
|> fetch_flash()
|
||||
|> UserAuth.require_authenticated_user([])
|
||||
|
||||
assert halted_conn.halted
|
||||
refute get_session(halted_conn, :user_return_to)
|
||||
end
|
||||
|
||||
test "does not redirect if user is authenticated", %{conn: conn, user: user} do
|
||||
conn = conn |> assign(:current_user, user) |> UserAuth.require_authenticated_user([])
|
||||
refute conn.halted
|
||||
refute conn.status
|
||||
end
|
||||
end
|
||||
end
|
Binary file not shown.
|
@ -28,12 +28,7 @@ defmodule Bright.StreamsFixtures do
|
|||
attrs
|
||||
|> Enum.into(%{
|
||||
ipfs_cid: "some ipfs_cid",
|
||||
mux_asset_id: "some mux_asset_id",
|
||||
mux_playback_id: "some mux_playback_id",
|
||||
s3_bucket: "some s3_bucket",
|
||||
s3_cdn_url: "some s3_cdn_url",
|
||||
s3_key: "some s3_key",
|
||||
s3_upload_id: "some s3_upload_id",
|
||||
torrent: "some torrent",
|
||||
playlist_url: "some playlist_url",
|
||||
thumbnail_url: "some thumbnail_url"
|
||||
|
|
Loading…
Reference in New Issue