diff --git a/devbox.json b/devbox.json index 962b648..af53baf 100644 --- a/devbox.json +++ b/devbox.json @@ -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" } } diff --git a/devbox.lock b/devbox.lock index 3fea3f4..65fd043 100644 --- a/devbox.lock +++ b/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", diff --git a/docker-compose.yml b/docker-compose.yml index 87c4280..cc7b53e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/dockerfiles/bright.dockerfile b/dockerfiles/bright.dockerfile index 51413ee..a9e6573 100644 --- a/dockerfiles/bright.dockerfile +++ b/dockerfiles/bright.dockerfile @@ -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"] diff --git a/services/bright/config/config.exs b/services/bright/config/config.exs index 1a4d2db..64affc0 100644 --- a/services/bright/config/config.exs +++ b/services/bright/config/config.exs @@ -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: [ diff --git a/services/bright/config/runtime.exs b/services/bright/config/runtime.exs index 08531d5..8b76c73 100644 --- a/services/bright/config/runtime.exs +++ b/services/bright/config/runtime.exs @@ -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, diff --git a/services/bright/lib/bright.ex b/services/bright/lib/bright.ex index 55d586e..cb24ebd 100644 --- a/services/bright/lib/bright.ex +++ b/services/bright/lib/bright.ex @@ -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 diff --git a/services/bright/lib/bright/blog.ex b/services/bright/lib/bright/blog.ex deleted file mode 100644 index 2e891fb..0000000 --- a/services/bright/lib/bright/blog.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/blog/post.ex b/services/bright/lib/bright/blog/post.ex deleted file mode 100644 index e5bc353..0000000 --- a/services/bright/lib/bright/blog/post.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/cache.ex b/services/bright/lib/bright/cache.ex index 24d2cef..ec32a92 100644 --- a/services/bright/lib/bright/cache.ex +++ b/services/bright/lib/bright/cache.ex @@ -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) diff --git a/services/bright/lib/bright/catalog.ex b/services/bright/lib/bright/catalog.ex deleted file mode 100644 index 5ced820..0000000 --- a/services/bright/lib/bright/catalog.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/catalog/category.ex b/services/bright/lib/bright/catalog/category.ex deleted file mode 100644 index 50ac10a..0000000 --- a/services/bright/lib/bright/catalog/category.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/catalog/product.ex b/services/bright/lib/bright/catalog/product.ex deleted file mode 100644 index cee7218..0000000 --- a/services/bright/lib/bright/catalog/product.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/downloader.ex b/services/bright/lib/bright/downloader.ex index 16354fd..2660e75 100644 --- a/services/bright/lib/bright/downloader.ex +++ b/services/bright/lib/bright/downloader.ex @@ -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]) diff --git a/services/bright/lib/bright/events.ex b/services/bright/lib/bright/events.ex new file mode 100644 index 0000000..28891ff --- /dev/null +++ b/services/bright/lib/bright/events.ex @@ -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 diff --git a/services/bright/lib/bright/images.ex b/services/bright/lib/bright/images.ex index 71b5001..f50d619 100644 --- a/services/bright/lib/bright/images.ex +++ b/services/bright/lib/bright/images.ex @@ -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 diff --git a/services/bright/lib/bright/mailer.ex b/services/bright/lib/bright/mailer.ex deleted file mode 100644 index 1c82853..0000000 --- a/services/bright/lib/bright/mailer.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule Bright.Mailer do - use Swoosh.Mailer, otp_app: :bright -end diff --git a/services/bright/lib/bright/oban_workers/create_hls_playlist.ex b/services/bright/lib/bright/oban_workers/create_hls_playlist.ex index c2d9645..98990ec 100644 --- a/services/bright/lib/bright/oban_workers/create_hls_playlist.ex +++ b/services/bright/lib/bright/oban_workers/create_hls_playlist.ex @@ -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/: #{inspect(failed)}") - {:error, :failed} + {:error, e, %Oban.Job{attempt: attempt, max_attempts: max_attempts}} -> + Streams.broadcast_processing_failed!(vod, attempt, max_attempts) + {:error, e} 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"} - ] end end diff --git a/services/bright/lib/bright/oban_workers/create_hls_playlist.ex.old b/services/bright/lib/bright/oban_workers/create_hls_playlist.ex.old new file mode 100644 index 0000000..c924bdb --- /dev/null +++ b/services/bright/lib/bright/oban_workers/create_hls_playlist.ex.old @@ -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/: #{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 diff --git a/services/bright/lib/bright/oban_workers/create_thumbnail.ex b/services/bright/lib/bright/oban_workers/create_thumbnail.ex index 18ace3d..0113846 100644 --- a/services/bright/lib/bright/oban_workers/create_thumbnail.ex +++ b/services/bright/lib/bright/oban_workers/create_thumbnail.ex @@ -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} diff --git a/services/bright/lib/bright/orders.ex b/services/bright/lib/bright/orders.ex deleted file mode 100644 index 458506b..0000000 --- a/services/bright/lib/bright/orders.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/orders/line_item.ex b/services/bright/lib/bright/orders/line_item.ex deleted file mode 100644 index a94009f..0000000 --- a/services/bright/lib/bright/orders/line_item.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/orders/order.ex b/services/bright/lib/bright/orders/order.ex deleted file mode 100644 index 44c5ba7..0000000 --- a/services/bright/lib/bright/orders/order.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/shopping_cart.ex b/services/bright/lib/bright/shopping_cart.ex deleted file mode 100644 index d2ecdb1..0000000 --- a/services/bright/lib/bright/shopping_cart.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/storage.ex b/services/bright/lib/bright/storage.ex new file mode 100644 index 0000000..84e5e3a --- /dev/null +++ b/services/bright/lib/bright/storage.ex @@ -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 diff --git a/services/bright/lib/bright/streams.ex b/services/bright/lib/bright/streams.ex index 5e41794..21ff355 100644 --- a/services/bright/lib/bright/streams.ex +++ b/services/bright/lib/bright/streams.ex @@ -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 diff --git a/services/bright/lib/bright/streams/vod.ex b/services/bright/lib/bright/streams/vod.ex index 0cf648e..87d1cb6 100644 --- a/services/bright/lib/bright/streams/vod.ex +++ b/services/bright/lib/bright/streams/vod.ex @@ -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 diff --git a/services/bright/lib/bright/user.ex b/services/bright/lib/bright/user.ex deleted file mode 100644 index 3e04b20..0000000 --- a/services/bright/lib/bright/user.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/user_from_auth.ex b/services/bright/lib/bright/user_from_auth.ex deleted file mode 100644 index 440f1b6..0000000 --- a/services/bright/lib/bright/user_from_auth.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright/vtubers.ex b/services/bright/lib/bright/vtubers.ex index 0cc7130..7461ed6 100644 --- a/services/bright/lib/bright/vtubers.ex +++ b/services/bright/lib/bright/vtubers.ex @@ -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 diff --git a/services/bright/lib/bright_web/components/layouts/root.html.heex b/services/bright/lib/bright_web/components/layouts/root.html.heex index 00c461a..6aac60c 100644 --- a/services/bright/lib/bright_web/components/layouts/root.html.heex +++ b/services/bright/lib/bright_web/components/layouts/root.html.heex @@ -118,11 +118,13 @@ <% else %> <.link - href={~p"/users/register"} + href={~p"/auth/github"} + method="get" class="navbar-item" > - Register + Sign in via GH + <%#

hello

%> <%# <.link href={~p"/auth/github"} class="navbar-item" @@ -139,6 +141,49 @@ + <%= @inner_content %> diff --git a/services/bright/lib/bright_web/controllers/auth_html.ex b/services/bright/lib/bright_web/controllers/auth_html.ex deleted file mode 100644 index 04a3779..0000000 --- a/services/bright/lib/bright_web/controllers/auth_html.ex +++ /dev/null @@ -1,7 +0,0 @@ -defmodule BrightWeb.AuthHTML do - use BrightWeb, :html - - - embed_templates "auth_html/*" - -end diff --git a/services/bright/lib/bright_web/controllers/auth_html/request.html.heex b/services/bright/lib/bright_web/controllers/auth_html/request.html.heex deleted file mode 100644 index 0387678..0000000 --- a/services/bright/lib/bright_web/controllers/auth_html/request.html.heex +++ /dev/null @@ -1 +0,0 @@ -

hello this is request.html

\ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/cart_controller.ex b/services/bright/lib/bright_web/controllers/cart_controller.ex deleted file mode 100644 index 9f52a2d..0000000 --- a/services/bright/lib/bright_web/controllers/cart_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/cart_html.ex b/services/bright/lib/bright_web/controllers/cart_html.ex deleted file mode 100644 index c4e25d5..0000000 --- a/services/bright/lib/bright_web/controllers/cart_html.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/cart_html/show.html.heex b/services/bright/lib/bright_web/controllers/cart_html/show.html.heex deleted file mode 100644 index 46e9ccf..0000000 --- a/services/bright/lib/bright_web/controllers/cart_html/show.html.heex +++ /dev/null @@ -1,26 +0,0 @@ - - -<.header> - My Cart - <:subtitle :if={@cart.items == []}>Your cart is empty - <:actions> - <.link href={~p"/orders"} method="post"> - <.button>Complete order - - - - -
- <.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))} - - <:actions> - <.button>Update cart - - - Total: {currency_to_str(ShoppingCart.total_cart_price(@cart))} -
- -<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/cart_item_controller.ex b/services/bright/lib/bright_web/controllers/cart_item_controller.ex deleted file mode 100644 index 3372521..0000000 --- a/services/bright/lib/bright_web/controllers/cart_item_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/hello_controller.ex b/services/bright/lib/bright_web/controllers/hello_controller.ex deleted file mode 100644 index 1d3a3ef..0000000 --- a/services/bright/lib/bright_web/controllers/hello_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/hello_html.ex b/services/bright/lib/bright_web/controllers/hello_html.ex deleted file mode 100644 index 40a235d..0000000 --- a/services/bright/lib/bright_web/controllers/hello_html.ex +++ /dev/null @@ -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""" -

Hello World, from {@messenger}!

- """ - end -end diff --git a/services/bright/lib/bright_web/controllers/hello_html/index.html.heex b/services/bright/lib/bright_web/controllers/hello_html/index.html.heex deleted file mode 100644 index b01c4a2..0000000 --- a/services/bright/lib/bright_web/controllers/hello_html/index.html.heex +++ /dev/null @@ -1,3 +0,0 @@ -
-

Hello World, from Phoenix!~

-
\ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/hello_html/show.html.heex b/services/bright/lib/bright_web/controllers/hello_html/show.html.heex deleted file mode 100644 index 5cc1584..0000000 --- a/services/bright/lib/bright_web/controllers/hello_html/show.html.heex +++ /dev/null @@ -1,3 +0,0 @@ -
- <.greet messenger={@messenger} /> -
\ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/order_controller.ex b/services/bright/lib/bright_web/controllers/order_controller.ex deleted file mode 100644 index 88a8440..0000000 --- a/services/bright/lib/bright_web/controllers/order_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/order_html.ex b/services/bright/lib/bright_web/controllers/order_html.ex deleted file mode 100644 index 5813054..0000000 --- a/services/bright/lib/bright_web/controllers/order_html.ex +++ /dev/null @@ -1,4 +0,0 @@ -defmodule BrightWeb.OrderHTML do - use BrightWeb, :html - embed_templates "order_html/*" -end diff --git a/services/bright/lib/bright_web/controllers/order_html/show.html.heex b/services/bright/lib/bright_web/controllers/order_html/show.html.heex deleted file mode 100644 index df41e43..0000000 --- a/services/bright/lib/bright_web/controllers/order_html/show.html.heex +++ /dev/null @@ -1,20 +0,0 @@ -<.header> - Thank you for your order! - <:subtitle> - User uuid: {@order.user_uuid} - - - - -<.table id="items" rows={@order.line_items}> - <:col :let={item} label="Title">{item.product.title} - <:col :let={item} label="Quantity">{item.quantity} - <:col :let={item} label="Price"> - {BrightWeb.CartHTML.currency_to_str(item.price)} - - - -Total price: -{BrightWeb.CartHTML.currency_to_str(@order.total_price)} - -<.back navigate={~p"/products"}>Back to products \ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/page_controller.ex b/services/bright/lib/bright_web/controllers/page_controller.ex index e95330a..6ba6f1d 100644 --- a/services/bright/lib/bright_web/controllers/page_controller.ex +++ b/services/bright/lib/bright_web/controllers/page_controller.ex @@ -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 diff --git a/services/bright/lib/bright_web/controllers/page_html/about.html.heex b/services/bright/lib/bright_web/controllers/page_html/about.html.heex index 7987ab5..ae9e19f 100644 --- a/services/bright/lib/bright_web/controllers/page_html/about.html.heex +++ b/services/bright/lib/bright_web/controllers/page_html/about.html.heex @@ -3,10 +3,16 @@
-
-

About

+
+
+

Dedication to the preservation of Lewdtuber history

+

+
+
-

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.

+
+ +

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.

diff --git a/services/bright/lib/bright_web/controllers/page_html/home.html.heex b/services/bright/lib/bright_web/controllers/page_html/home.html.heex index ad2b3d5..dca830e 100644 --- a/services/bright/lib/bright_web/controllers/page_html/home.html.heex +++ b/services/bright/lib/bright_web/controllers/page_html/home.html.heex @@ -26,20 +26,6 @@
- - diff --git a/services/bright/lib/bright_web/controllers/page_html/profile.html.heex b/services/bright/lib/bright_web/controllers/page_html/profile.html.heex new file mode 100644 index 0000000..dbadb58 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/page_html/profile.html.heex @@ -0,0 +1,47 @@ +<.flash_group flash={@flash} /> + + +<%= if @current_user do%> +
+

Profile

+

{@current_user.name}

+ + +
+ + +
+ +
+
+
+
+
+ {@current_user.name} +
+
+
+

{@current_user.name}

+

Github User {@current_user.github_id}

+
+
+ +
+

Futureporn User {@current_user.id}

+

n uploads

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec + iaculis mauris. @bulmaio. #css + #responsive +
+ +
+
+
+ +
+<% else %> +

Please <.link href={~p"/auth/github"}>sign in

+<% end %> \ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/product_controller.ex b/services/bright/lib/bright_web/controllers/product_controller.ex deleted file mode 100644 index 5984005..0000000 --- a/services/bright/lib/bright_web/controllers/product_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/product_html.ex b/services/bright/lib/bright_web/controllers/product_html.ex deleted file mode 100644 index 1ad749a..0000000 --- a/services/bright/lib/bright_web/controllers/product_html.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/product_html/edit.html.heex b/services/bright/lib/bright_web/controllers/product_html/edit.html.heex deleted file mode 100644 index 5179947..0000000 --- a/services/bright/lib/bright_web/controllers/product_html/edit.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - Edit Product {@product.id} - <:subtitle>Use this form to manage product records in the database. - - -<.product_form changeset={@changeset} action={~p"/products/#{@product}"} /> - -<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/product_html/index.html.heex b/services/bright/lib/bright_web/controllers/product_html/index.html.heex deleted file mode 100644 index 687b052..0000000 --- a/services/bright/lib/bright_web/controllers/product_html/index.html.heex +++ /dev/null @@ -1,26 +0,0 @@ -<.header> - Listing Products - <:actions> - <.link href={~p"/products/new"}> - <.button>New Product - - - - -<.table id="products" rows={@products} row_click={&JS.navigate(~p"/products/#{&1}")}> - <:col :let={product} label="Title">{product.title} - <:col :let={product} label="Description">{product.description} - <:col :let={product} label="Price">{product.price} - <:col :let={product} label="Views">{product.views} - <:action :let={product}> -
- <.link navigate={~p"/products/#{product}"}>Show -
- <.link navigate={~p"/products/#{product}/edit"}>Edit - - <:action :let={product}> - <.link href={~p"/products/#{product}"} method="delete" data-confirm="Are you sure?"> - Delete - - - diff --git a/services/bright/lib/bright_web/controllers/product_html/new.html.heex b/services/bright/lib/bright_web/controllers/product_html/new.html.heex deleted file mode 100644 index e64df6b..0000000 --- a/services/bright/lib/bright_web/controllers/product_html/new.html.heex +++ /dev/null @@ -1,8 +0,0 @@ -<.header> - New Product - <:subtitle>Use this form to manage product records in the database. - - -<.product_form changeset={@changeset} action={~p"/products"} /> - -<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex b/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex deleted file mode 100644 index d2dc1ba..0000000 --- a/services/bright/lib/bright_web/controllers/product_html/product_form.html.heex +++ /dev/null @@ -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. - - <.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 - - diff --git a/services/bright/lib/bright_web/controllers/product_html/show.html.heex b/services/bright/lib/bright_web/controllers/product_html/show.html.heex deleted file mode 100644 index a29e6f7..0000000 --- a/services/bright/lib/bright_web/controllers/product_html/show.html.heex +++ /dev/null @@ -1,26 +0,0 @@ -<.header> - Product {@product.id} - <:subtitle>This is a product record from the database. - <:actions> - <.link href={~p"/products/#{@product}/edit"}> - <.button>Edit product - - <.link href={~p"/cart_items?product_id=#{@product.id}"} method="post"> - <.button>Add to cart - - - - -<.list> - <:item title="Title">{@product.title} - <:item title="Description">{@product.description} - <:item title="Price">{@product.price} - <:item title="Views">{@product.views} - <:item title="Categories"> -
    -
  • {cat.title}
  • -
- - - -<.back navigate={~p"/products"}>Back to products diff --git a/services/bright/lib/bright_web/controllers/user_controller.ex b/services/bright/lib/bright_web/controllers/user_controller.ex deleted file mode 100644 index c9c922b..0000000 --- a/services/bright/lib/bright_web/controllers/user_controller.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/controllers/user_html.ex b/services/bright/lib/bright_web/controllers/user_html.ex deleted file mode 100644 index 60eac6b..0000000 --- a/services/bright/lib/bright_web/controllers/user_html.ex +++ /dev/null @@ -1,4 +0,0 @@ -defmodule BrightWeb.UserHTML do - use BrightWeb, :html - embed_templates "user_html/*" -end diff --git a/services/bright/lib/bright_web/controllers/user_html/join.html.heex b/services/bright/lib/bright_web/controllers/user_html/join.html.heex deleted file mode 100644 index ed6e1ab..0000000 --- a/services/bright/lib/bright_web/controllers/user_html/join.html.heex +++ /dev/null @@ -1,7 +0,0 @@ - -<.header> - Join Futureporn - - - -<.user_form changeset={@changeset} action={~p"/join"} /> \ No newline at end of file diff --git a/services/bright/lib/bright_web/controllers/user_html/show.html.heex b/services/bright/lib/bright_web/controllers/user_html/show.html.heex deleted file mode 100644 index a595a7e..0000000 --- a/services/bright/lib/bright_web/controllers/user_html/show.html.heex +++ /dev/null @@ -1,11 +0,0 @@ - -<.header> - Visitor Profile - - -<%= if @current_user do %> -

@current_user is {@current_user}

-<% else %> -

there is no @current_user

-<% end %> - diff --git a/services/bright/lib/bright_web/controllers/user_html/user_form.html.heex b/services/bright/lib/bright_web/controllers/user_html/user_form.html.heex deleted file mode 100644 index 989e9bd..0000000 --- a/services/bright/lib/bright_web/controllers/user_html/user_form.html.heex +++ /dev/null @@ -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. - - <.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 - - - diff --git a/services/bright/lib/bright_web/controllers/user_session_controller.ex b/services/bright/lib/bright_web/controllers/user_session_controller.ex new file mode 100644 index 0000000..bb69007 --- /dev/null +++ b/services/bright/lib/bright_web/controllers/user_session_controller.ex @@ -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 diff --git a/services/bright/lib/bright_web/controllers/vod_controller.ex b/services/bright/lib/bright_web/controllers/vod_controller.ex index 9714258..e4bb204 100644 --- a/services/bright/lib/bright_web/controllers/vod_controller.ex +++ b/services/bright/lib/bright_web/controllers/vod_controller.ex @@ -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 diff --git a/services/bright/lib/bright_web/controllers/vod_html/index.html.heex b/services/bright/lib/bright_web/controllers/vod_html/index.html.heex index af2e106..6cfd299 100644 --- a/services/bright/lib/bright_web/controllers/vod_html/index.html.heex +++ b/services/bright/lib/bright_web/controllers/vod_html/index.html.heex @@ -8,6 +8,7 @@ <.table id="vods" rows={@vods} row_click={&JS.navigate(~p"/vods/#{&1}")}> + <%# <:col :let={vod} label="Uploader">{vod.uploaded_by_id} %> <:col :let={vod} label="ID">{vod.id} <:col :let={vod} label="S3 CDN URL">{vod.s3_cdn_url} <:col :let={vod} label="Mux asset">{vod.mux_asset_id} diff --git a/services/bright/lib/bright_web/controllers/vod_html/show.html.heex b/services/bright/lib/bright_web/controllers/vod_html/show.html.heex index cace0f5..e0ffe67 100644 --- a/services/bright/lib/bright_web/controllers/vod_html/show.html.heex +++ b/services/bright/lib/bright_web/controllers/vod_html/show.html.heex @@ -46,6 +46,7 @@ <.list> + <%# <:item title="Uploader"> %> <:item title="Source VOD File"> <%= if @vod.s3_cdn_url do %> @@ -53,7 +54,7 @@ <% end %> - <:item title="Thumbnail URL"> + <:item title="Thumbnail"> <:item title="HLS Playlist URL">{@vod.playlist_url} <:item title="Torrent">{@vod.torrent} <:item title="Ipfs CID">{@vod.ipfs_cid} diff --git a/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex b/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex index 1f9f31b..baa2088 100644 --- a/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex +++ b/services/bright/lib/bright_web/controllers/vod_html/vod_form.html.heex @@ -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. @@ -12,6 +13,7 @@ <.input field={f[:notes]} type="textarea" label="Notes" /> <.input field={f[:stream_id]} type="select" label="Stream" multiple={false} options={stream_opts(@changeset)}/> + <:actions> <.button>Save Vod diff --git a/services/bright/lib/bright_web/live/post_live/form_component.ex b/services/bright/lib/bright_web/live/post_live/form_component.ex deleted file mode 100644 index 94db4e6..0000000 --- a/services/bright/lib/bright_web/live/post_live/form_component.ex +++ /dev/null @@ -1,83 +0,0 @@ -defmodule BrightWeb.PostLive.FormComponent do - use BrightWeb, :live_component - - alias Bright.Blog - - @impl true - def render(assigns) do - ~H""" -
- <.header> - {@title} - <:subtitle>Use this form to manage post records in the database. - - - <.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 - - -
- """ - 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 diff --git a/services/bright/lib/bright_web/live/post_live/index.ex b/services/bright/lib/bright_web/live/post_live/index.ex deleted file mode 100644 index a732076..0000000 --- a/services/bright/lib/bright_web/live/post_live/index.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/live/post_live/index.html.heex b/services/bright/lib/bright_web/live/post_live/index.html.heex deleted file mode 100644 index d361b94..0000000 --- a/services/bright/lib/bright_web/live/post_live/index.html.heex +++ /dev/null @@ -1,42 +0,0 @@ -<.header> - Listing Posts - <:actions> - <.link patch={~p"/posts/new"}> - <.button>New Post - - - - -<.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 :let={{_id, post}} label="Body">{post.body} - <:action :let={{_id, post}}> -
- <.link navigate={~p"/posts/#{post}"}>Show -
- <.link patch={~p"/posts/#{post}/edit"}>Edit - - <:action :let={{id, post}}> - <.link - phx-click={JS.push("delete", value: %{id: post.id}) |> hide("##{id}")} - data-confirm="Are you sure?" - > - Delete - - - - -<.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"} - /> - diff --git a/services/bright/lib/bright_web/live/post_live/show.ex b/services/bright/lib/bright_web/live/post_live/show.ex deleted file mode 100644 index c974a2c..0000000 --- a/services/bright/lib/bright_web/live/post_live/show.ex +++ /dev/null @@ -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 diff --git a/services/bright/lib/bright_web/live/post_live/show.html.heex b/services/bright/lib/bright_web/live/post_live/show.html.heex deleted file mode 100644 index 954f97e..0000000 --- a/services/bright/lib/bright_web/live/post_live/show.html.heex +++ /dev/null @@ -1,34 +0,0 @@ -<.header> - Post {@post.id} - <:subtitle>This is a post record from the database. - <:actions> - <.link patch={~p"/posts/#{@post}/show/edit"} phx-click={JS.push_focus()}> - <.button>Edit post - - - - - - - - - - - -<.list> - <:item title="Title">{@post.title} - <:item title="Body">{@post.body} - - -<.back navigate={~p"/posts"}>Back to posts - -<.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}"} - /> - diff --git a/services/bright/lib/bright_web/live/thermostat_live.ex b/services/bright/lib/bright_web/live/thermostat_live.ex deleted file mode 100644 index 280db96..0000000 --- a/services/bright/lib/bright_web/live/thermostat_live.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule BrightWeb.ThermostatLive do - use BrightWeb, :live_view - - def render(assigns) do - ~H""" - Current temperature: {@temperature}°F - - """ - 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 diff --git a/services/bright/lib/bright_web/router.ex b/services/bright/lib/bright_web/router.ex index f6a9c4a..259c61f 100644 --- a/services/bright/lib/bright_web/router.ex +++ b/services/bright/lib/bright_web/router.ex @@ -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 diff --git a/services/bright/lib/bright_web/controllers/auth_controller.ex b/services/bright/lib/bright_web/user_auth.ex similarity index 50% rename from services/bright/lib/bright_web/controllers/auth_controller.ex rename to services/bright/lib/bright_web/user_auth.ex index 5f4c07d..0f1e022 100644 --- a/services/bright/lib/bright_web/controllers/auth_controller.ex +++ b/services/bright/lib/bright_web/user_auth.ex @@ -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 diff --git a/services/bright/mix.exs b/services/bright/mix.exs index bee29b2..a52f834 100644 --- a/services/bright/mix.exs +++ b/services/bright/mix.exs @@ -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 diff --git a/services/bright/mix.lock b/services/bright/mix.lock index cee8fe5..caff7b3 100644 --- a/services/bright/mix.lock +++ b/services/bright/mix.lock @@ -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"}, } diff --git a/services/bright/priv/repo/migrations/20250126020211_remove_auth_and_users.exs b/services/bright/priv/repo/migrations/20250126020211_remove_auth_and_users.exs new file mode 100644 index 0000000..49df9b7 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250126020211_remove_auth_and_users.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250126022331_create_users_auth_tables.exs b/services/bright/priv/repo/migrations/20250126022331_create_users_auth_tables.exs new file mode 100644 index 0000000..45039cd --- /dev/null +++ b/services/bright/priv/repo/migrations/20250126022331_create_users_auth_tables.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250126200332_add_uploaded_by_to_vods.exs b/services/bright/priv/repo/migrations/20250126200332_add_uploaded_by_to_vods.exs new file mode 100644 index 0000000..3de3c9e --- /dev/null +++ b/services/bright/priv/repo/migrations/20250126200332_add_uploaded_by_to_vods.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250127063219_add_github_id.exs b/services/bright/priv/repo/migrations/20250127063219_add_github_id.exs new file mode 100644 index 0000000..b0a9bad --- /dev/null +++ b/services/bright/priv/repo/migrations/20250127063219_add_github_id.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250127072102_add_user_avatar_name.exs b/services/bright/priv/repo/migrations/20250127072102_add_user_avatar_name.exs new file mode 100644 index 0000000..0760979 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250127072102_add_user_avatar_name.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250127073215_remove_email_and_password.exs b/services/bright/priv/repo/migrations/20250127073215_remove_email_and_password.exs new file mode 100644 index 0000000..552b301 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250127073215_remove_email_and_password.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250128040801_add_local_path.exs b/services/bright/priv/repo/migrations/20250128040801_add_local_path.exs new file mode 100644 index 0000000..4a1ad4d --- /dev/null +++ b/services/bright/priv/repo/migrations/20250128040801_add_local_path.exs @@ -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 diff --git a/services/bright/priv/repo/migrations/20250128043513_add_duration.exs b/services/bright/priv/repo/migrations/20250128043513_add_duration.exs new file mode 100644 index 0000000..7133616 --- /dev/null +++ b/services/bright/priv/repo/migrations/20250128043513_add_duration.exs @@ -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 diff --git a/services/bright/test.txt b/services/bright/test.txt deleted file mode 100644 index 875a0a2..0000000 --- a/services/bright/test.txt +++ /dev/null @@ -1,3 +0,0 @@ -HELLO MAMA - -haha \ No newline at end of file diff --git a/services/bright/test/bright/images_test.exs b/services/bright/test/bright/images_test.exs index 39e65f4..509a8c0 100644 --- a/services/bright/test/bright/images_test.exs +++ b/services/bright/test/bright/images_test.exs @@ -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: <> 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 diff --git a/services/bright/test/bright/oban_workers/create_thumbnail_test.exs b/services/bright/test/bright/oban_workers/create_thumbnail_test.exs index 31de3c0..05796c7 100644 --- a/services/bright/test/bright/oban_workers/create_thumbnail_test.exs +++ b/services/bright/test/bright/oban_workers/create_thumbnail_test.exs @@ -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 diff --git a/services/bright/test/bright/streams_test.exs b/services/bright/test/bright/streams_test.exs index 7849dcc..88a5991 100644 --- a/services/bright/test/bright/streams_test.exs +++ b/services/bright/test/bright/streams_test.exs @@ -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 diff --git a/services/bright/test/bright_web/live/post_live_test.exs b/services/bright/test/bright_web/live/post_live_test.exs deleted file mode 100644 index af2b7d9..0000000 --- a/services/bright/test/bright_web/live/post_live_test.exs +++ /dev/null @@ -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 diff --git a/services/bright/test/bright_web/user_auth_test.exs b/services/bright/test/bright_web/user_auth_test.exs deleted file mode 100644 index 07653df..0000000 --- a/services/bright/test/bright_web/user_auth_test.exs +++ /dev/null @@ -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 diff --git a/services/bright/test/fixtures/test-fixture.ts b/services/bright/test/fixtures/test-fixture.ts new file mode 100644 index 0000000..43d67d9 Binary files /dev/null and b/services/bright/test/fixtures/test-fixture.ts differ diff --git a/services/bright/test/support/fixtures/streams_fixtures.ex b/services/bright/test/support/fixtures/streams_fixtures.ex index 5e53dc2..1b6fe86 100644 --- a/services/bright/test/support/fixtures/streams_fixtures.ex +++ b/services/bright/test/support/fixtures/streams_fixtures.ex @@ -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"