From 4c7550303025f034c7b2454d292308dcc65b0a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pepe=20M=C3=A1rquez=20Romero?= Date: Mon, 13 Apr 2026 20:55:21 +0200 Subject: [PATCH] fixes, local development and qr in device setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pepe Márquez Romero --- config/config.exs | 2 - config/dev.exs | 20 ++++- config/runtime.exs | 36 ++++---- docker-compose.dev.yml | 35 ++++++++ lib/goatmire/accounts/user.ex | 3 + lib/goatmire/accounts/user_token.ex | 3 + lib/goatmire/gallery_slideshow.ex | 23 +++-- lib/goatmire/media.ex | 56 ++++++++++-- lib/goatmire_web/live/device_config_live.ex | 15 ++++ lib/goatmire_web/live/home_live.ex | 2 +- mix.exs | 5 +- mix.lock | 9 +- test/goatmire/accounts_test.exs | 2 +- test/goatmire/media_test.exs | 31 ++++++- .../channels/device_gallery_channel_test.exs | 19 +--- .../controllers/page_controller_test.exs | 2 +- .../user_session_controller_test.exs | 15 +--- .../live/user_live/confirmation_test.exs | 2 +- .../live/user_live/login_test.exs | 86 +------------------ .../live/user_live/registration_test.exs | 82 ------------------ 20 files changed, 208 insertions(+), 240 deletions(-) create mode 100644 docker-compose.dev.yml delete mode 100644 test/goatmire_web/live/user_live/registration_test.exs diff --git a/config/config.exs b/config/config.exs index c88afd1..81bd5e8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -84,8 +84,6 @@ config :goatmire, Goatmire.Repo, migration_foreign_Key: [column: :id, type: :binary_id], migration_timestamps: [type: :utc_datetime] -config :rustler_precompiled, :force_build, typst: true - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index c65cf05..4fdd3b6 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -14,9 +14,7 @@ config :goatmire, Goatmire.Repo, # watchers to your application. For example, we can use it # to bundle .js and .css sources. config :goatmire, GoatmireWeb.Endpoint, - # Binding to loopback ipv4 address prevents access from other machines. - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: String.to_integer(System.get_env("PORT") || "4000")], + http: [ip: {0, 0, 0, 0}, port: String.to_integer(System.get_env("PORT") || "4000")], check_origin: false, code_reloader: true, debug_errors: true, @@ -83,3 +81,19 @@ config :phoenix_live_view, # Disable swoosh api client as it is only required for production adapters. config :swoosh, :api_client, false + +# MinIO (local S3) config for development +# Uses mDNS hostname - works for both local (via hosts) and network devices +minio_host = System.get_env("MINIO_HOST") || "pepe-laptop.local" + +config :ex_aws, + json_codec: Jason, + access_key_id: "minioadmin", + secret_access_key: "minioadmin" + +config :ex_aws, :s3, + scheme: "http://", + host: minio_host, + port: 9000, + region: "us-east-1", + bucket: "goatmire" diff --git a/config/runtime.exs b/config/runtime.exs index 42795ac..c9ae114 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -20,26 +20,26 @@ if System.get_env("PHX_SERVER") do config :goatmire, GoatmireWeb.Endpoint, server: true end -# Tigris config -config :ex_aws, - debug_requests: true, - json_codec: Jason, - access_key_id: - System.get_env("AWS_ACCESS_KEY_ID") || - raise("Missing env variable: AWS_ACCESS_KEY_ID"), - secret_access_key: - System.get_env("AWS_SECRET_ACCESS_KEY") || - raise("Missing env variable: AWS_SECRET_ACCESS_KEY") +if config_env() == :prod do + # Tigris config + config :ex_aws, + debug_requests: true, + json_codec: Jason, + access_key_id: + System.get_env("AWS_ACCESS_KEY_ID") || + raise("Missing env variable: AWS_ACCESS_KEY_ID"), + secret_access_key: + System.get_env("AWS_SECRET_ACCESS_KEY") || + raise("Missing env variable: AWS_SECRET_ACCESS_KEY") -config :ex_aws, :s3, - scheme: "https://", - host: "fly.storage.tigris.dev", - region: "auto", - bucket: - System.get_env("BUCKET_NAME") || - raise("Missing env variable: BUCKET_NAME") + config :ex_aws, :s3, + scheme: "https://", + host: "fly.storage.tigris.dev", + region: "auto", + bucket: + System.get_env("BUCKET_NAME") || + raise("Missing env variable: BUCKET_NAME") -if config_env() == :prod do database_path = System.get_env("DATABASE_PATH") || raise """ diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..f85078b --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,35 @@ +services: + minio: + image: minio/minio:RELEASE.2024-11-07T00-52-20Z + container_name: goatmire-minio + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + command: server /data --console-address ":9001" + volumes: + - minio-data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 5s + retries: 5 + + create-bucket: + image: minio/mc:RELEASE.2025-08-13T08-35-41Z + container_name: goatmire-create-bucket + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + mc alias set local http://minio:9000 minioadmin minioadmin; + mc mb local/goatmire --ignore-existing; + mc anonymous set public local/goatmire; + exit 0; + " + +volumes: + minio-data: \ No newline at end of file diff --git a/lib/goatmire/accounts/user.ex b/lib/goatmire/accounts/user.ex index 9e58b5d..939f3ec 100644 --- a/lib/goatmire/accounts/user.ex +++ b/lib/goatmire/accounts/user.ex @@ -2,6 +2,9 @@ defmodule Goatmire.Accounts.User do use Ecto.Schema import Ecto.Changeset + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + schema "users" do field :email, :string field :password, :string, virtual: true, redact: true diff --git a/lib/goatmire/accounts/user_token.ex b/lib/goatmire/accounts/user_token.ex index c17d860..ef3b013 100644 --- a/lib/goatmire/accounts/user_token.ex +++ b/lib/goatmire/accounts/user_token.ex @@ -3,6 +3,9 @@ defmodule Goatmire.Accounts.UserToken do import Ecto.Query alias Goatmire.Accounts.UserToken + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + @hash_algorithm :sha256 @rand_size 32 diff --git a/lib/goatmire/gallery_slideshow.ex b/lib/goatmire/gallery_slideshow.ex index 2edd6ca..cfe9d58 100644 --- a/lib/goatmire/gallery_slideshow.ex +++ b/lib/goatmire/gallery_slideshow.ex @@ -1,6 +1,8 @@ defmodule Goatmire.GallerySlideshow do use GenServer + require Logger + alias Goatmire.Media def start_link(opts \\ []) do @@ -16,13 +18,22 @@ defmodule Goatmire.GallerySlideshow do end @impl true - def handle_info(:update, state) do - [image | rest] = - case state.photos do - [] -> Media.list_approved_images() |> Enum.shuffle() - photos -> photos - end + def handle_info(:update, %{photos: []} = state) do + case Media.list_approved_images() |> Enum.shuffle() do + [] -> + Logger.warning("No approved images available for slideshow") + {:noreply, state} + + [image | rest] -> + broadcast_and_update(image, rest, state) + end + end + + def handle_info(:update, %{photos: [image | rest]} = state) do + broadcast_and_update(image, rest, state) + end + defp broadcast_and_update(image, rest, state) do GoatmireWeb.Endpoint.broadcast("device_gallery", "image", %{ url: Goatmire.Utils.presigned_url(image.dithered_key) }) diff --git a/lib/goatmire/media.ex b/lib/goatmire/media.ex index 497629f..caf061f 100644 --- a/lib/goatmire/media.ex +++ b/lib/goatmire/media.ex @@ -53,6 +53,55 @@ defmodule Goatmire.Media do iex> create_image(%{field: bad_value}) {:error, %Ecto.Changeset{}} + """ + def create_image(attrs) do + %Image{} + |> Image.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a image. + + ## Examples + + iex> update_image(image, %{field: new_value}) + {:ok, %Image{}} + + iex> update_image(image, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_image(%Image{} = image, attrs) do + image + |> Image.changeset(attrs) + |> Repo.update() + end + + @doc """ + Returns all images. + + ## Examples + + iex> list_images() + [%Image{}, ...] + + """ + def list_images do + Repo.all(Image) + end + + @doc """ + Creates a image and processes it. + + ## Examples + + iex> create_and_process_image(%{field: value}) + {:ok, %Image{}} + + iex> create_and_process_image(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + """ def create_and_process_image(attrs) do %Image{} @@ -72,16 +121,13 @@ defmodule Goatmire.Media do end @doc """ - Updates a image. + Updates image processing keys. ## Examples - iex> update_image(image, %{field: new_value}) + iex> update_image_keys(image, %{dithered_key: "..."}) {:ok, %Image{}} - iex> update_image(image, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - """ def update_image_keys(%Image{} = image, attrs) do image diff --git a/lib/goatmire_web/live/device_config_live.ex b/lib/goatmire_web/live/device_config_live.ex index 4bd520e..e440475 100644 --- a/lib/goatmire_web/live/device_config_live.ex +++ b/lib/goatmire_web/live/device_config_live.ex @@ -93,6 +93,20 @@ defmodule GoatmireWeb.DeviceConfigLive do +
+ + QR display settings (optional) + +
+ <.input + field={f[:qr_link]} + type="url" + label="QR link" + placeholder="https://example.com" + /> +
+
+
Preview
@@ -132,6 +146,7 @@ defmodule GoatmireWeb.DeviceConfigLive do |> Map.put_new("greeting_size", 24) |> Map.put_new("company_size", 24) |> Map.put_new("spacing", 24) + |> Map.put_new("qr_link", "") socket = socket diff --git a/lib/goatmire_web/live/home_live.ex b/lib/goatmire_web/live/home_live.ex index 955f6c4..0236450 100644 --- a/lib/goatmire_web/live/home_live.ex +++ b/lib/goatmire_web/live/home_live.ex @@ -56,7 +56,7 @@ defmodule GoatmireWeb.HomeLive do
<.input field={f[:alt_text]} - label="Image description (alt text)" + label="Image description (alt text - minimum 20 characters)" phx-debounce="blur" required /> diff --git a/mix.exs b/mix.exs index 66a0454..a38145b 100644 --- a/mix.exs +++ b/mix.exs @@ -73,8 +73,9 @@ defmodule Goatmire.MixProject do {:ex_aws_s3, "~> 2.0"}, {:hackney, "~> 1.9"}, {:sweet_xml, "~> 0.6.6"}, - {:dither, github: "protolux-electronics/dither"}, - {:typst, github: "gworkman/typst"} + {:dither, "~> 0.1.1"}, + {:typst, "== 0.3.2"}, + {:rustler, ">= 0.0.0"} ] end diff --git a/mix.lock b/mix.lock index cddedbc..9c16402 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,12 @@ %{ "bandit": {:hex, :bandit, "1.8.0", "c2e93d7e3c5c794272fa4623124f827c6f24b643acc822be64c826f9447d92fb", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [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", "8458ff4eed20ff2a2ea69d4854883a077c33ea42b51f6811b044ceee0fa15422"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.3.2", "d50091e3c9492d73e17fc1e1619a9b09d6a5ef99160eb4d736926fd475a16ca3", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "471be5151874ae7931911057d1467d908955f93554f7a6cd1b7d804cac8cef53"}, - "castore": {:hex, :castore, "1.0.15", "8aa930c890fe18b6fe0a0cff27b27d0d4d231867897bd23ea772dee561f032a3", [:mix], [], "hexpm", "96ce4c69d7d5d7a0761420ef743e2f4096253931a3ba69e5ff8ef1844fe446d3"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, - "circuits_gpio": {:hex, :circuits_gpio, "2.1.2", "b4efcf84e6a74910b9fd0488d9fc8177d1cf80bb585782caaafc91bb035a74a4", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4b7c9de074a3c00937c01b0cd8781dd9114b1eb4705e99e7a22d46ad4899e157"}, - "circuits_spi": {:hex, :circuits_spi, "2.0.4", "b75f64c0401e3c64319dcfc76a9bc17466e4469adc5daf34165f4c4b11cf12ee", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "ccf034065091f26c624dee777ea3f48ce64696af812622e1d060b2cffdbf90e4"}, "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"}, "db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, - "dither": {:git, "https://github.com/protolux-electronics/dither.git", "4949b10a272a8d803a03b8c69cec956f6aea4468", []}, + "dither": {:hex, :dither, "0.1.1", "1d3bb11c043427e0c5f5828ef324e9aac6d295ad4eb9b6e1cd6d8521947142d0", [:mix], [{:rustler, "~> 0.36.2", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "3f8f21275e8e8bf2b5f65f272b647d9dd630d19b0d03006c8c948d8c993d63f0"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "ecto": {:hex, :ecto, "3.13.2", "7d0c0863f3fc8d71d17fc3ad3b9424beae13f02712ad84191a826c7169484f01", [: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", "669d9291370513ff56e7b7e7081b7af3283d02e046cf3d403053c557894a0b3e"}, "ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [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", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"}, @@ -54,7 +51,7 @@ "req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [: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", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"}, "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"}, "rustler": {:hex, :rustler, "0.36.2", "6c2142f912166dfd364017ab2bf61242d4a5a3c88e7b872744642ae004b82501", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.7", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "93832a6dbc1166739a19cd0c25e110e4cf891f16795deb9361dfcae95f6c88fe"}, - "rustler_precompiled": {:hex, :rustler_precompiled, "0.8.3", "4e741024b0b097fe783add06e53ae9a6f23ddc78df1010f215df0c02915ef5a8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "c23f5f33cb6608542de4d04faf0f0291458c352a4648e4d28d17ee1098cddcc4"}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"}, "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, @@ -67,7 +64,7 @@ "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, "thousand_island": {:hex, :thousand_island, "1.4.0", "7189aa9046cb4dc50e5d1d515cd4be6e1c34b6755b2becee624784ab8b07b4df", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "686ff52b9e4cf1e16d2bd8ba83360f2357ac6ca304250702ecc3b40000f04015"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, - "typst": {:git, "https://github.com/gworkman/typst.git", "a5eb5d661cfbbb547a180cf8afdc62f06e5a7b2a", []}, + "typst": {:hex, :typst, "0.3.2", "70be6ef00fcffc4052839b1c5e2abee535d5940192e66c618bcf87f54b942c29", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "a63d0d0fabc5fb7a6163fdb6226abdd2fd850e9e83530f65098b0f9052893b82"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"}, "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"}, diff --git a/test/goatmire/accounts_test.exs b/test/goatmire/accounts_test.exs index 0fbacc9..dc26c9f 100644 --- a/test/goatmire/accounts_test.exs +++ b/test/goatmire/accounts_test.exs @@ -38,7 +38,7 @@ defmodule Goatmire.AccountsTest do describe "get_user!/1" do test "raises if id is invalid" do assert_raise Ecto.NoResultsError, fn -> - Accounts.get_user!(-1) + Accounts.get_user!(Ecto.UUID.generate()) end end diff --git a/test/goatmire/media_test.exs b/test/goatmire/media_test.exs index a01208d..7ae01f1 100644 --- a/test/goatmire/media_test.exs +++ b/test/goatmire/media_test.exs @@ -8,7 +8,15 @@ defmodule Goatmire.MediaTest do import Goatmire.MediaFixtures - @invalid_attrs %{s3_key: nil, thumbnail_key: nil, dithered_key: nil, alt_text: nil, accept_terms: nil, approved_at: nil, rejected_at: nil} + @invalid_attrs %{ + s3_key: nil, + thumbnail_key: nil, + dithered_key: nil, + alt_text: nil, + accept_terms: nil, + approved_at: nil, + rejected_at: nil + } test "list_images/0 returns all images" do image = image_fixture() @@ -21,7 +29,15 @@ defmodule Goatmire.MediaTest do end test "create_image/1 with valid data creates a image" do - valid_attrs = %{s3_key: "some s3_key", thumbnail_key: "some thumbnail_key", dithered_key: "some dithered_key", alt_text: "some alt_text", accept_terms: true, approved_at: ~U[2025-08-30 15:00:00Z], rejected_at: ~U[2025-08-30 15:00:00Z]} + valid_attrs = %{ + s3_key: "some s3_key", + thumbnail_key: "some thumbnail_key", + dithered_key: "some dithered_key", + alt_text: "some alt_text", + accept_terms: true, + approved_at: ~U[2025-08-30 15:00:00Z], + rejected_at: ~U[2025-08-30 15:00:00Z] + } assert {:ok, %Image{} = image} = Media.create_image(valid_attrs) assert image.s3_key == "some s3_key" @@ -39,7 +55,16 @@ defmodule Goatmire.MediaTest do test "update_image/2 with valid data updates the image" do image = image_fixture() - update_attrs = %{s3_key: "some updated s3_key", thumbnail_key: "some updated thumbnail_key", dithered_key: "some updated dithered_key", alt_text: "some updated alt_text", accept_terms: false, approved_at: ~U[2025-08-31 15:00:00Z], rejected_at: ~U[2025-08-31 15:00:00Z]} + + update_attrs = %{ + s3_key: "some updated s3_key", + thumbnail_key: "some updated thumbnail_key", + dithered_key: "some updated dithered_key", + alt_text: "some updated alt_text", + accept_terms: false, + approved_at: ~U[2025-08-31 15:00:00Z], + rejected_at: ~U[2025-08-31 15:00:00Z] + } assert {:ok, %Image{} = image} = Media.update_image(image, update_attrs) assert image.s3_key == "some updated s3_key" diff --git a/test/goatmire_web/channels/device_gallery_channel_test.exs b/test/goatmire_web/channels/device_gallery_channel_test.exs index ed6e033..f6e8268 100644 --- a/test/goatmire_web/channels/device_gallery_channel_test.exs +++ b/test/goatmire_web/channels/device_gallery_channel_test.exs @@ -4,24 +4,13 @@ defmodule GoatmireWeb.DeviceGalleryChannelTest do setup do {:ok, _, socket} = GoatmireWeb.DeviceSocket - |> socket("user_id", %{some: :assign}) - |> subscribe_and_join(GoatmireWeb.DeviceGalleryChannel, "device_gallery:lobby") + |> socket("device_id", %{some: :assign}) + |> subscribe_and_join(GoatmireWeb.DeviceGalleryChannel, "device_gallery") %{socket: socket} end - test "ping replies with status ok", %{socket: socket} do - ref = push(socket, "ping", %{"hello" => "there"}) - assert_reply ref, :ok, %{"hello" => "there"} - end - - test "shout broadcasts to device_gallery:lobby", %{socket: socket} do - push(socket, "shout", %{"hello" => "all"}) - assert_broadcast "shout", %{"hello" => "all"} - end - - test "broadcasts are pushed to the client", %{socket: socket} do - broadcast_from!(socket, "broadcast", %{"some" => "data"}) - assert_push "broadcast", %{"some" => "data"} + test "join succeeds", %{socket: socket} do + assert socket.topic == "device_gallery" end end diff --git a/test/goatmire_web/controllers/page_controller_test.exs b/test/goatmire_web/controllers/page_controller_test.exs index 41e7743..0b624f0 100644 --- a/test/goatmire_web/controllers/page_controller_test.exs +++ b/test/goatmire_web/controllers/page_controller_test.exs @@ -3,6 +3,6 @@ defmodule GoatmireWeb.PageControllerTest do test "GET /", %{conn: conn} do conn = get(conn, ~p"/") - assert html_response(conn, 200) =~ "Peace of mind from prototype to production" + assert html_response(conn, 200) =~ "Upload an image" end end diff --git a/test/goatmire_web/controllers/user_session_controller_test.exs b/test/goatmire_web/controllers/user_session_controller_test.exs index d9ae508..003c1d0 100644 --- a/test/goatmire_web/controllers/user_session_controller_test.exs +++ b/test/goatmire_web/controllers/user_session_controller_test.exs @@ -20,12 +20,9 @@ defmodule GoatmireWeb.UserSessionControllerTest do assert get_session(conn, :user_token) assert redirected_to(conn) == ~p"/" - # Now do a logged in request and assert on the menu conn = get(conn, ~p"/") response = html_response(conn, 200) - assert response =~ user.email - assert response =~ ~p"/users/settings" - assert response =~ ~p"/users/log-out" + assert response =~ "Welcome back!" end test "logs the user in with remember me", %{conn: conn, user: user} do @@ -84,12 +81,9 @@ defmodule GoatmireWeb.UserSessionControllerTest do assert get_session(conn, :user_token) assert redirected_to(conn) == ~p"/" - # Now do a logged in request and assert on the menu conn = get(conn, ~p"/") response = html_response(conn, 200) - assert response =~ user.email - assert response =~ ~p"/users/settings" - assert response =~ ~p"/users/log-out" + assert response =~ "Welcome back!" end test "confirms unconfirmed user", %{conn: conn, unconfirmed_user: user} do @@ -108,12 +102,9 @@ defmodule GoatmireWeb.UserSessionControllerTest do assert Accounts.get_user!(user.id).confirmed_at - # Now do a logged in request and assert on the menu conn = get(conn, ~p"/") response = html_response(conn, 200) - assert response =~ user.email - assert response =~ ~p"/users/settings" - assert response =~ ~p"/users/log-out" + assert response =~ "User confirmed successfully" end test "redirects to login page when magic link is invalid", %{conn: conn} do diff --git a/test/goatmire_web/live/user_live/confirmation_test.exs b/test/goatmire_web/live/user_live/confirmation_test.exs index 8787f7f..6d1041c 100644 --- a/test/goatmire_web/live/user_live/confirmation_test.exs +++ b/test/goatmire_web/live/user_live/confirmation_test.exs @@ -29,7 +29,7 @@ defmodule GoatmireWeb.UserLive.ConfirmationTest do {:ok, _lv, html} = live(conn, ~p"/users/log-in/#{token}") refute html =~ "Confirm my account" - assert html =~ "Log in" + assert html =~ "Keep me logged in" end test "confirms the given token once", %{conn: conn, unconfirmed_user: user} do diff --git a/test/goatmire_web/live/user_live/login_test.exs b/test/goatmire_web/live/user_live/login_test.exs index 57c1efe..5248d8c 100644 --- a/test/goatmire_web/live/user_live/login_test.exs +++ b/test/goatmire_web/live/user_live/login_test.exs @@ -9,83 +9,8 @@ defmodule GoatmireWeb.UserLive.LoginTest do {:ok, _lv, html} = live(conn, ~p"/users/log-in") assert html =~ "Log in" - assert html =~ "Register" - assert html =~ "Log in with email" - end - end - - describe "user login - magic link" do - test "sends magic link email when user exists", %{conn: conn} do - user = user_fixture() - - {:ok, lv, _html} = live(conn, ~p"/users/log-in") - - {:ok, _lv, html} = - form(lv, "#login_form_magic", user: %{email: user.email}) - |> render_submit() - |> follow_redirect(conn, ~p"/users/log-in") - - assert html =~ "If your email is in our system" - - assert Goatmire.Repo.get_by!(Goatmire.Accounts.UserToken, user_id: user.id).context == - "login" - end - - test "does not disclose if user is registered", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/log-in") - - {:ok, _lv, html} = - form(lv, "#login_form_magic", user: %{email: "idonotexist@example.com"}) - |> render_submit() - |> follow_redirect(conn, ~p"/users/log-in") - - assert html =~ "If your email is in our system" - end - end - - describe "user login - password" do - test "redirects if user logs in with valid credentials", %{conn: conn} do - user = user_fixture() |> set_password() - - {:ok, lv, _html} = live(conn, ~p"/users/log-in") - - form = - form(lv, "#login_form_password", - user: %{email: user.email, password: valid_user_password(), remember_me: true} - ) - - conn = submit_form(form, conn) - - assert redirected_to(conn) == ~p"/" - end - - test "redirects to login page with a flash error if credentials are invalid", %{ - conn: conn - } do - {:ok, lv, _html} = live(conn, ~p"/users/log-in") - - form = - form(lv, "#login_form_password", user: %{email: "test@email.com", password: "123456"}) - - render_submit(form, %{user: %{remember_me: true}}) - - conn = follow_trigger_action(form, conn) - assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid email or password" - assert redirected_to(conn) == ~p"/users/log-in" - end - end - - describe "login navigation" do - test "redirects to registration page when the Register button is clicked", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/log-in") - - {:ok, _login_live, login_html} = - lv - |> element("main a", "Sign up") - |> render_click() - |> follow_redirect(conn, ~p"/users/register") - - assert login_html =~ "Register" + assert html =~ "Email" + assert html =~ "Password" end end @@ -99,11 +24,8 @@ defmodule GoatmireWeb.UserLive.LoginTest do {:ok, _lv, html} = live(conn, ~p"/users/log-in") assert html =~ "You need to reauthenticate" - refute html =~ "Register" - assert html =~ "Log in with email" - - assert html =~ - ~s( log_in_user(user_fixture()) - |> live(~p"/users/register") - |> follow_redirect(conn, ~p"/") - - assert {:ok, _conn} = result - end - - test "renders errors for invalid data", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") - - result = - lv - |> element("#registration_form") - |> render_change(user: %{"email" => "with spaces"}) - - assert result =~ "Register" - assert result =~ "must have the @ sign and no spaces" - end - end - - describe "register user" do - test "creates account but does not log in", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") - - email = unique_user_email() - form = form(lv, "#registration_form", user: valid_user_attributes(email: email)) - - {:ok, _lv, html} = - render_submit(form) - |> follow_redirect(conn, ~p"/users/log-in") - - assert html =~ - ~r/An email was sent to .*, please access it to confirm your account/ - end - - test "renders errors for duplicated email", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") - - user = user_fixture(%{email: "test@email.com"}) - - result = - lv - |> form("#registration_form", - user: %{"email" => user.email} - ) - |> render_submit() - - assert result =~ "has already been taken" - end - end - - describe "registration navigation" do - test "redirects to login page when the Log in button is clicked", %{conn: conn} do - {:ok, lv, _html} = live(conn, ~p"/users/register") - - {:ok, _login_live, login_html} = - lv - |> element("main a", "Log in") - |> render_click() - |> follow_redirect(conn, ~p"/users/log-in") - - assert login_html =~ "Log in" - end - end -end