diff --git a/.gitignore b/.gitignore index 3b0f885..a155b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ Dockerfile-jackjoe .env.* docker-compose.yml node_modules +.serena +.playwright-mcp diff --git a/.tool-versions b/.tool-versions index 0bf057e..874d2f5 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,6 +1,6 @@ -elixir 1.17.2 -erlang 27.0.1 -nodejs 20.17.0 +elixir 1.19.5-otp-28 +erlang 28.1 +nodejs 24.13.0 rebar 3.22.1 -pnpm 9.9.0 +pnpm 10.28.2 yarn 1.22.19 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3861f87..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -dist: xenial -language: elixir -#cache: apt -# services: -# - mysql -before_script: - - wget https://repo.mysql.com//mysql-apt-config_0.8.24-1_all.deb - - sudo dpkg -i mysql-apt-config_0.8.24-1_all.deb - - sudo apt-get update -q || true - - sudo apt-get install -q -y --allow-unauthenticated -o Dpkg::Options::=--force-confnew mysql-server - - sudo systemctl restart mysql - - sudo mysql_upgrade - - mysql --version - - mysql -e 'CREATE DATABASE mailgun_logger_ci_test;' -matrix: - include: - - elixir: 1.13 - otp_release: 24.0 -notifications: - recipients: - - randy@jackjoe.be diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f7487..8f43bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,94 @@ +## 2603.2.0 (2026-03-11) + +**BREAKING** + +This is a major infrastructure upgrade. Please review the breaking changes below before upgrading. + +### Added + +- Dark/light/system theme support with user preference stored in database +- Theme picker in user edit form (system/light/dark) +- Inline JS in layout for instant theme application (no flash-of-wrong-theme) +- System theme option respects `prefers-color-scheme` media query and reacts to OS changes + +### Security + +- Use distinct `signing_salt` for LiveView (was identical to `secret_key_base`) +- Add `SECRET_KEY_BASE` env var override in `releases.exs` +- Remove `check_origin: false` from base config (keep in `dev.exs` only) +- Disable `debug_errors` in production config +- Remove API key from Mailgun client URLs (was logged in plaintext via `Logger.debug`) +- Guard S3 uploads with `store_messages` config check; replace `ExAws.request!` with error-handling `ExAws.request` +- Use `String.to_existing_atom/1` for log level env var (prevents atom table exhaustion) + +### Changed + +- Complete UI overhaul: industrial/utilitarian dark theme with monospace typography throughout +- Replaced inline styles in templates with semantic CSS classes +- Login page: centered card layout +- Tables: dense rows, uppercase mono headers, no rounded corners +- Navigation: sticky header, amber accent underline for active state +- Alerts: left-border accent style (amber for warnings, red for errors, green for info) +- Forms: dark inset inputs with amber focus borders +- All CSS via custom properties for easy theming +- Removed obsolete `X-UA-Compatible` meta tag and empty `description` meta tag +- Fixed `target="blank"` to `target="_blank"` in event links +- Removed stale hard-coded subnav breadcrumb from layout +- All templates converted from `.eex` to `.heex` (HEEx) +- Layout partials (head/foot) inlined into layout templates +- `~L` sigils (LEEx) replaced with `~H` (HEEx) throughout +- `link/2` helpers replaced with `<.link>` components +- `form_for` replaced with `<.form>` components +- `phx-feedback-for` and `phx-no-feedback` removed from core components (removed in LiveView 1.0) +- `Flop.Phoenix.cursor_pagination` replaced with `Flop.Phoenix.pagination` +- Added `compilers: [:phoenix_live_view]` and `listeners: [Phoenix.CodeReloader]` to mix.exs (Phoenix 1.8 requirement) +- Endpoint: `gzip: false` -> `gzip: not code_reloading?` +- Config: `config :logger, :console` -> `config :logger, :default_formatter` +- Config: `import_config "#{Mix.env()}.exs"` -> `"#{config_env()}.exs"` +- Fixed test config referencing `MailgunLogger.Endpoint` instead of `MailgunLoggerWeb.Endpoint` +- Fixed core_components referencing wrong Gettext module +- Added `lazy_html` test dependency (LiveView 1.1 requirement) +- Dev config: added `phoenix_live_view` debug annotations and expensive runtime checks + +### Performance + +- Add time filter to `get_stats/1` query (was scanning entire events table) +- Use `Repo.aggregate` for account count instead of loading all rows + +### Removed + +- Dead modules: `Pager`, `Pager.Page`, `RunTriggererLive`, `UserSocket`, `PagingHelpers` +- Unused functions: `Users.assign_role/2`, `Emails.test_mail/1`, duplicate `Events.bucket/0` +- Unused socket mount from endpoint +- Unused `ml_pagesize` config +- Dead Papertrail config from `releases.exs` +- Stale `config :phoenix, :serve_endpoints` from prod config +- Stale `root: "."` from prod endpoint config +- Duplicate `config :logger` block in `config.exs` +- Removed `:phoenix_live_view` from compilers (obsolete since LiveView 0.18) +- Removed `import Phoenix.LiveView.Helpers` (removed in LiveView 1.0) + +### Fixed + +- `IO.inspect(changeset)` left in user controller error path +- `event["delivery-status"]["attempt-no"]` crash on non-delivery events (use `get_in/2`) + +### Breaking changes + +- **Removed `logger_papertrail_backend`** dependency and all Papertrail logging configuration. If you relied on Papertrail for log forwarding, you will need to set up an alternative solution. +- **Removed `:backends` logger configuration**. Elixir 1.19 / OTP 28 deprecates the `:backends` key. Logger now uses the default Erlang handler. +- **`Argon2.check_pass/2`** replaced with `Argon2.verify_pass/2` — no user-facing impact, but custom auth code referencing the old API will need updating. + +### Upgraded + +- **Phoenix 1.7 -> 1.8.5** +- **Phoenix LiveView 0.20 -> 1.1.27** +- **Ecto SQL 3.11 -> 3.13**, Postgrex 0.20 -> 0.22 +- **Flop 0.25 -> 0.26**, Flop Phoenix 0.22 -> 0.25 +- **Gettext 0.16 -> 0.25** (now uses `Gettext.Backend`) +- Plug 1.7 -> 1.19, Plug Cowboy 2.1 -> 2.8 +- Elixir requirement: ~> 1.17, tested on Elixir 1.19.5 / OTP 28 + ## 2410.1.0 (2024-10-09) - Fix: prevent creation of admins when setup has been done diff --git a/README.md b/README.md index 6d56024..a0db9ba 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ Since version 2302.1.0 (Feb 2023) the database has switched from MySQL to Postgr -[![Build Status](https://app.travis-ci.com/jackjoe/mailgun_logger.svg?branch=master)](https://app.travis-ci.com/jackjoe/mailgun_logger) - Simple Mailgun log persistence in Phoenix/Elixir. MailgunLogger is a simple admin tool that uses the Mailgun API to retrieves events on a regular basis from Mailgun - who only provide a limited time of event storage - and stores them inside a PostgreSQL database. @@ -34,6 +32,12 @@ _This is NOT meant as a replacement for the excellent online tooling provided by _Jack + Joe is not responsible for your use of this tool, neither for any persistence guarantees. Free comes at a price :)_ +## Requirements + +- Elixir ~> 1.17 +- Phoenix ~> 1.8 +- PostgreSQL + ## Versioning We use a simplified version numbering in the format of: @@ -78,11 +82,15 @@ Following variables are available: - **ML_DB_PASSWORD**: database password - **ML_DB_NAME**: database name - **ML_DB_HOST**: database host +- **SECRET_KEY_BASE**: secret key base for Phoenix (required for releases) +- **HOST**: application host (e.g. `localhost`) +- **PORT**: application port (default: `5050`) [Optional] +- **ML_DB_PORT**: database port (default: `5432`) +- **ML_PAGESIZE**: events per page (default: `50`) - **ML_STORE_MESSAGES**: when true, persist messages to the S3 bucket described below, defaults to false -- **ML_PAGESIZE**: events per page - **ML_LOG_LEVEL**: log level (info, debug, warn, ...) - **MAILGUN_API_KEY**: to send the password reset email - **MAILGUN_DOMAIN**: to send the password reset email @@ -106,6 +114,8 @@ $ docker run -d -p 5050:5050 \ -e "ML_DB_PASSWORD=password" \ -e "ML_DB_NAME=mailgun_logger" \ -e "ML_DB_HOST=my_db_host" \ + -e "SECRET_KEY_BASE=your-secret-key-base" \ + -e "HOST=localhost" \ --name mailgun_logger jackjoe/mailgun_logger ``` @@ -114,8 +124,6 @@ $ docker run -d -p 5050:5050 \ With the following `docker-compose.yml`: ```yml -version: "3" - services: db: image: postgres @@ -142,6 +150,8 @@ services: - "ML_DB_PASSWORD=logger" - "ML_DB_NAME=mailgun_logger" - "ML_DB_HOST=db" + - "SECRET_KEY_BASE=generate-a-secret-key-base" + - "HOST=localhost" - "MAILGUN_API_KEY=*****" - "MAILGUN_DOMAIN=m.mydomain.be" - "AWS_ACCESS_KEY_ID=******" @@ -175,7 +185,7 @@ Then head over to [http://0.0.0.0:5050](http://0.0.0.0:5050). If you find yourself with many events in your database and a slow search, consider installing the [**pg_trm** extension](https://www.postgresql.org/docs/current/pgtrgm.html). Adding an index on these 3 fields will give you a huge speed gain: -```mysql +```sql CREATE EXTENSION pg_trgm; CREATE INDEX events_recipient_gin_trgm_idx ON events USING gin (recipient gin_trgm_ops); @@ -189,12 +199,13 @@ To run on your local machine, you need to setup shop first. Mailgun Logger requires a PostgreSQL database using the following environment variables along with their defaults: ```elixir -# config/config.ex +# config/config.exs config :mailgun_logger, MailgunLogger.Repo, - username: System.get_env("ML_DB_USER", "mailgun_logger_ci"), - password: System.get_env("ML_DB_PASSWORD", "johndoe"), - database: System.get_env("ML_DB_NAME", "mailgun_logger_ci_test"), - hostname: System.get_env("ML_DB_HOST", "localhost"), + username: System.get_env("ML_DB_USER") || "root", + password: System.get_env("ML_DB_PASSWORD"), + database: System.get_env("ML_DB_NAME") || "mailgun_logger_dev", + hostname: System.get_env("ML_DB_HOST") || "localhost", + port: System.get_env("ML_DB_PORT") || 5432 ``` Either export your own enviroment variables or adhere to the defaults. Then, for convenience, run: diff --git a/config/config.exs b/config/config.exs index ecc6ce5..0bf72c5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -9,16 +9,15 @@ config :mailgun_logger, ecto_repos: [MailgunLogger.Repo], env: Mix.env(), store_messages: System.get_env("ML_STORE_MESSAGES") || false, - ml_pagesize: System.get_env("ML_PAGESIZE") || "100" + page_size: String.to_integer(System.get_env("ML_PAGESIZE", "50")) # Configures the endpoint config :mailgun_logger, MailgunLoggerWeb.Endpoint, url: [host: System.get_env("HOST")], secret_key_base: "9zFYul0/t5smQYyvAsFKC+Lk3AQbQrMw9Fp/OgOOJGQtHEn1dvH6WmdH26mGvv2d", render_errors: [view: MailgunLoggerWeb.ErrorView, accepts: ~w(html json)], - live_view: [signing_salt: "9zFYul0/t5smQYyvAsFKC+Lk3AQbQrMw9Fp/OgOOJGQtHEn1dvH6WmdH26mGvv2d"], - pubsub_server: MailgunLogger.PubSub, - check_origin: false + live_view: [signing_salt: "ML_LV_Salt"], + pubsub_server: MailgunLogger.PubSub # Configure esbuild (the version is required) config :esbuild, @@ -30,23 +29,15 @@ config :esbuild, env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} ] -config :phoenix, :format_encoders, json: Jason -config :phoenix, :json_library, Jason +config :phoenix, + format_encoders: [json: Jason], + json_library: Jason # config :scrivener_html, # routes_helper: MailgunLoggerWeb.Router.Helpers, # # If you use a single view style everywhere, you can configure it here. See View Styles below for more info. # view_style: :bootstrap -# Configures Elixir's Logger -config :logger, :console, - format: "$time $metadata[$level] $message\n", - metadata: [:user_id] - -config :logger, - backends: [:console], - level: String.to_atom(System.get_env("ML_LOG_LEVEL", "debug")) || :debug - config :flop, repo: MailgunLogger.Repo config :ex_aws, @@ -90,16 +81,10 @@ config :mailgun_logger, MailgunLogger.Repo, # Configures Elixir's Logger config :logger, - backends: [:console], - level: :debug, - console: [ + level: String.to_existing_atom(System.get_env("ML_LOG_LEVEL", "debug")), + default_formatter: [ format: "$time $metadata[$level] $message\n", metadata: [:user_id] - ], - logger_papertrail_backend: [ - host: System.get_env("PAPERTRAIL_HOST"), - system_name: "mailgun-logger", - format: "$metadata $message" ] -import_config "#{Mix.env()}.exs" +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 57492b3..16b67ca 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -15,11 +15,11 @@ config :mailgun_logger, MailgunLoggerWeb.Endpoint, certfile: "priv/cert/selfsigned.pem" ], live_reload: [ + web_console_logger: true, patterns: [ ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$}, ~r{priv/gettext/.*(po)$}, - ~r{lib/mailgun_logger_web/views/.*(ex)$}, - ~r{lib/mailgun_logger_web/templates/.*(eex)$} + ~r{lib/mailgun_logger_web/(?:controllers|components|lives|views|templates|router)/?.*\.(ex|heex)$} ] ], debug_errors: true, @@ -36,5 +36,9 @@ config :mailgun_logger, MailgunLogger.Scheduler, jobs: [] # config :mailgun_logger, MailgunLogger.Mailer, adapter: Bamboo.LocalAdapter # Do not include metadata nor timestamps in development logs -config :logger, :console, format: "[$level] $message\n" +config :logger, :default_formatter, format: "[$level] $message\n" config :phoenix, :stacktrace_depth, 20 + +config :phoenix_live_view, + debug_heex_annotations: true, + enable_expensive_runtime_checks: true diff --git a/config/prod.exs b/config/prod.exs index 52c401b..deec65e 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -4,15 +4,12 @@ config :mailgun_logger, MailgunLoggerWeb.Endpoint, http: [port: System.fetch_env!("PORT"), compress: true], url: [host: System.fetch_env!("HOST"), port: 80], cache_static_manifest: "priv/static/cache_manifest.json", - debug_errors: true, + debug_errors: false, code_reloader: false, - root: ".", version: Application.spec(:mailgun_logger, :vsn) -config :phoenix, :serve_endpoints, true config :logger, - backends: [:console, LoggerPapertrailBackend.Logger], level: String.to_existing_atom(System.get_env("ML_LOG_LEVEL", "info")) || :info, compile_time_purge_matching: [[application: :remote_ip]] diff --git a/config/releases.exs b/config/releases.exs index a427ac0..6194d16 100644 --- a/config/releases.exs +++ b/config/releases.exs @@ -1,5 +1,8 @@ import Config +config :mailgun_logger, MailgunLoggerWeb.Endpoint, + secret_key_base: System.fetch_env!("SECRET_KEY_BASE") + config :mailgun_logger, MailgunLogger.Repo, username: System.get_env("ML_DB_USER"), password: System.get_env("ML_DB_PASSWORD"), @@ -13,8 +16,6 @@ config :mailgun_logger, MailgunLogger.Mailer, domain: System.get_env("MAILGUN_DOMAIN"), from: System.get_env("MAILGUN_FROM") || "no-reply@jackjoe.be" -config :logger, logger_papertrail_backend: [host: System.get_env("PAPERTRAIL_HOST")] - config :ex_aws, access_key_id: System.get_env("AWS_ACCESS_KEY_ID"), secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"), diff --git a/config/test.exs b/config/test.exs index 8336c38..09d1fdc 100644 --- a/config/test.exs +++ b/config/test.exs @@ -2,18 +2,15 @@ import Config # We don't run a server during test. If one is required, # you can enable the server option below. -config :mailgun_logger, MailgunLogger.Endpoint, +config :mailgun_logger, MailgunLoggerWeb.Endpoint, http: [port: 4001], server: false # Quantum cron schedule config :mailgun_logger, MailgunLogger.Scheduler, - jobs: [ - # Every hour - # {"50 * * * *", {MailgunLogger, :run, []}} - ] + jobs: [] -config :logger, level: :warn +config :logger, level: :warning config :mailgun_logger, MailgunLogger.Repo, pool: Ecto.Adapters.SQL.Sandbox config :mailgun_logger, MailgunLogger.Repo, diff --git a/lib/mailgun/client.ex b/lib/mailgun/client.ex index 37ae1d2..942d573 100644 --- a/lib/mailgun/client.ex +++ b/lib/mailgun/client.ex @@ -12,10 +12,9 @@ defmodule Mailgun.Client do } alias Mailgun.Client - require Logger - @base_url "https://api:API_KEY@api.mailgun.net/v3" - @base_eu_url "https://api:API_KEY@api.eu.mailgun.net/v3" + @base_url "https://api.mailgun.net/v3" + @base_eu_url "https://api.eu.mailgun.net/v3" @ttl 60_000 @request_opts [recv_timeout: @ttl, timeout: @ttl] @@ -31,7 +30,7 @@ defmodule Mailgun.Client do defp get_page(client, url, acc \\ []) do # Logger.info "Mailgun client :: get_page :: #{url}" - Logger.debug(Atom.to_string(:get) <> " - " <> url) + Logger.debug("get" <> " - " <> url) :get |> HTTPoison.request!(url, "", [], request_opts(client)) @@ -39,7 +38,7 @@ defmodule Mailgun.Client do end defp _request(method, client, url, body \\ "") do - Logger.debug(Atom.to_string(method) <> " - " <> url) + Logger.debug("#{method} - #{url}") method |> HTTPoison.request!(url, body, [], request_opts(client)) @@ -112,7 +111,6 @@ defmodule Mailgun.Client do def url(%Client{is_eu: false} = client, path), do: _url(@base_url, client, path) defp _url(base_url, client, path) do - base_url = base_url |> String.replace("API_KEY", client.api_key) (base_url <> path) |> String.replace("DOMAIN", client.domain) end diff --git a/lib/mailgun/events.ex b/lib/mailgun/events.ex index ada56ca..a865a34 100644 --- a/lib/mailgun/events.ex +++ b/lib/mailgun/events.ex @@ -42,7 +42,10 @@ defmodule Mailgun.Events do url -> url = build_stored_message_url(client, url) stored_message = Client.get(client, url) - save_stored_message(event.api_id, stored_message) + + if Application.get_env(:mailgun_logger, :store_messages) do + save_stored_message(event.api_id, stored_message) + end case Events.has_stored_message(event) do {:ok, event} -> event @@ -53,9 +56,16 @@ defmodule Mailgun.Events do # Store on S3 defp save_stored_message(api_id, stored_message) do - bucket() - |> S3.put_object(file_path(api_id), Jason.encode!(stored_message)) - |> ExAws.request!() + case bucket() + |> S3.put_object(file_path(api_id), Jason.encode!(stored_message)) + |> ExAws.request() do + {:ok, _} -> + :ok + + {:error, reason} -> + Logger.error("Failed to save stored message #{api_id} to S3: #{inspect(reason)}") + :error + end end def get_stored_message(event) do diff --git a/lib/mailgun_logger/accounts/accounts.ex b/lib/mailgun_logger/accounts/accounts.ex index d7f33c4..0ca48b6 100644 --- a/lib/mailgun_logger/accounts/accounts.ex +++ b/lib/mailgun_logger/accounts/accounts.ex @@ -43,4 +43,9 @@ defmodule MailgunLogger.Accounts do def delete_account(%Account{} = account) do Repo.delete(account) end + + @spec count_accounts() :: non_neg_integer() + def count_accounts do + Repo.aggregate(Account, :count) + end end diff --git a/lib/mailgun_logger/emails.ex b/lib/mailgun_logger/emails.ex index 8c5946c..efa4398 100644 --- a/lib/mailgun_logger/emails.ex +++ b/lib/mailgun_logger/emails.ex @@ -31,13 +31,6 @@ defmodule MailgunLogger.Emails do |> log(:reset_password) end - def test_mail(recipient) do - base_email() - |> to(recipient) - |> subject("test") - |> text_body("test") - end - defp base_email do conf = Application.get_env(:mailgun_logger, MailgunLogger.Mailer) diff --git a/lib/mailgun_logger/events/event.ex b/lib/mailgun_logger/events/event.ex index 3bf90b6..968d46f 100644 --- a/lib/mailgun_logger/events/event.ex +++ b/lib/mailgun_logger/events/event.ex @@ -67,7 +67,7 @@ defmodule MailgunLogger.Event do }, default_pagination_type: :first, pagination_types: [:first, :last], - default_limit: 25 + default_limit: Application.compile_env(:mailgun_logger, :page_size, 50) } @type t :: %__MODULE__{ diff --git a/lib/mailgun_logger/events/events.ex b/lib/mailgun_logger/events/events.ex index 72066a8..51f9af6 100644 --- a/lib/mailgun_logger/events/events.ex +++ b/lib/mailgun_logger/events/events.ex @@ -25,24 +25,24 @@ defmodule MailgunLogger.Events do def init_filters(%{"filters" => filters} = params) do filters = - Enum.map(filters, fn {k, v} -> - v = - v - |> Enum.map(fn - {"value", v} when is_binary(v) -> {"value", String.trim(v)} - {k, v} -> {k, v} - end) - |> Enum.into(%{}) - - {k, v} + filters + |> Enum.map(fn {_k, v} -> + v + |> Enum.map(fn + {"value", v} when is_binary(v) -> {"value", String.trim(v)} + {k, v} -> {k, v} + end) + |> Enum.into(%{}) end) - |> Enum.into(%{}) + |> Enum.reject(fn v -> v["value"] == "" end) + |> Enum.with_index() + |> Enum.into(%{}, fn {v, i} -> {to_string(i), v} end) %{params | "filters" => filters} end def init_filters(params) do - Map.put(params, "filters", %{"0" => %{"field" => "event", "value" => "delivered"}}) + Map.put(params, "filters", %{}) end @spec get_event(number) :: Event.t() @@ -97,7 +97,7 @@ defmodule MailgunLogger.Events do |> Map.put("timestamp", conv_event_timestamp(event)) |> Map.put("api_id", event["id"]) |> Map.put("log_level", event["log-level"]) - |> Map.put("delivery_attempt", event["delivery-status"]["attempt-no"]) + |> Map.put("delivery_attempt", get_in(event, ["delivery-status", "attempt-no"])) |> Map.put("event", event["event"]) |> Map.put("message_from", message["headers"]["from"]) |> Map.put("message_to", message["headers"]["to"]) @@ -133,8 +133,12 @@ defmodule MailgunLogger.Events do end def get_stats(n_hours) do + now = DateTime.utc_now() + cutoff = DateTime.add(now, -n_hours * 3600) + stats = from(e in Event, + where: e.timestamp >= ^cutoff, group_by: [ e.event, fragment("CONCAT(to_char(?, 'yyyy-mm-dd hh24:mi:ss'), ?)", e.timestamp, ":00") @@ -189,5 +193,4 @@ defmodule MailgunLogger.Events do end end - def bucket(), do: Application.get_env(:ex_aws, :bucket) end diff --git a/lib/mailgun_logger/users/user.ex b/lib/mailgun_logger/users/user.ex index f137205..9ed75cf 100644 --- a/lib/mailgun_logger/users/user.ex +++ b/lib/mailgun_logger/users/user.ex @@ -20,6 +20,7 @@ defmodule MailgunLogger.User do encrypted_password: String.t(), reset_token: String.t(), password: String.t(), + theme: String.t(), roles: Ecto.Association.NotLoaded.t() | [Role.t()], inserted_at: NaiveDateTime.t(), updated_at: NaiveDateTime.t() @@ -32,6 +33,7 @@ defmodule MailgunLogger.User do field(:token, :string) field(:encrypted_password, :string) field(:reset_token, :string, default: nil) + field(:theme, :string, default: "system") field(:password, :string, virtual: true) many_to_many(:roles, Role, join_through: UserRole, on_replace: :delete) @@ -43,7 +45,7 @@ defmodule MailgunLogger.User do @spec changeset(User.t(), map()) :: Ecto.Changeset.t() def changeset(%User{} = user, attrs \\ %{}) do user - |> cast(attrs, [:firstname, :lastname, :email, :password]) + |> cast(attrs, [:firstname, :lastname, :email, :password, :theme]) |> validate_required([:email, :password]) |> update_change(:email, &String.downcase/1) |> validate_format(:email, @email_format) @@ -57,7 +59,7 @@ defmodule MailgunLogger.User do @spec update_changeset(User.t(), map()) :: Ecto.Changeset.t() def update_changeset(%User{} = user, attrs \\ %{}) do user - |> cast(attrs, [:firstname, :lastname, :email]) + |> cast(attrs, [:firstname, :lastname, :email, :theme]) |> update_change(:email, &String.downcase/1) |> validate_format(:email, @email_format) |> unique_constraint(:email) diff --git a/lib/mailgun_logger/users/users.ex b/lib/mailgun_logger/users/users.ex index 5f25064..ae6902e 100644 --- a/lib/mailgun_logger/users/users.ex +++ b/lib/mailgun_logger/users/users.ex @@ -3,7 +3,7 @@ defmodule MailgunLogger.Users do alias MailgunLogger.Repo alias MailgunLogger.User - alias MailgunLogger.Roles + @type ecto_user() :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @type maybe_user() :: User.t() | nil @@ -13,21 +13,6 @@ defmodule MailgunLogger.Users do Repo.all(User) |> Repo.preload(:roles) end - @spec assign_role(User.t(), String.t()) :: ecto_user() | nil - def assign_role(user, role_name) do - case Roles.get_role_by_name(role_name) do - nil -> - nil - - role -> - user - |> Repo.preload([:roles]) - |> User.changeset() - |> Ecto.Changeset.put_assoc(:roles, [role]) - |> Repo.update() - end - end - @spec get_user!(integer | String.t()) :: User.t() def get_user!(id) do from( @@ -87,7 +72,10 @@ defmodule MailgunLogger.Users do {:error, :unknown_user} user -> - Argon2.check_pass(user, password) + case Argon2.verify_pass(password, user.encrypted_password) do + true -> {:ok, user} + false -> {:error, "invalid password"} + end end end diff --git a/lib/mailgun_logger_web.ex b/lib/mailgun_logger_web.ex index 45a551c..1f00c61 100644 --- a/lib/mailgun_logger_web.ex +++ b/lib/mailgun_logger_web.ex @@ -21,9 +21,9 @@ defmodule MailgunLoggerWeb do def controller do quote do - use Phoenix.Controller, namespace: MailgunLoggerWeb + use Phoenix.Controller, formats: [html: "View", json: "View"] import Plug.Conn - import MailgunLoggerWeb.Gettext + use Gettext, backend: MailgunLoggerWeb.Gettext alias MailgunLoggerWeb.Router.Helpers, as: Routes unquote(verified_routes()) @@ -41,11 +41,6 @@ defmodule MailgunLoggerWeb do # Import convenience functions from controllers import Phoenix.Controller, only: [view_module: 1] - # TODO to be deleted and replaced with components - def render_partial(template, assigns \\ []) do - render(MailgunLoggerWeb.PartialView, template, assigns) - end - unquote(html_helpers()) end end @@ -62,7 +57,7 @@ defmodule MailgunLoggerWeb do def channel do quote do use Phoenix.Channel - import MailgunLoggerWeb.Gettext + use Gettext, backend: MailgunLoggerWeb.Gettext end end @@ -72,18 +67,12 @@ defmodule MailgunLoggerWeb do import Phoenix.HTML import Phoenix.HTML.Form use PhoenixHTMLHelpers - use Phoenix.LiveView - - # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) - import Phoenix.LiveView.Helpers # Import basic rendering functionality (render, render_layout, etc) import Phoenix.View - import MailgunLoggerWeb.Gettext + use Gettext, backend: MailgunLoggerWeb.Gettext import MailgunLoggerWeb.ErrorHelpers - import MailgunLoggerWeb.Gettext - import MailgunLoggerWeb.PagingHelpers import MailgunLoggerWeb.ViewHelpers alias MailgunLoggerWeb.Router.Helpers, as: Routes @@ -95,15 +84,11 @@ defmodule MailgunLoggerWeb do # Shortcut for generating JS commands alias Phoenix.LiveView.JS - - # Routes generation with the ~p sigil - unquote(verified_routes()) end end def component do quote do - # Use all HTML functionality (forms, tags, etc) use Phoenix.Component # Routes generation with the ~p sigil @@ -113,6 +98,7 @@ defmodule MailgunLoggerWeb do def live_view do quote do + use Phoenix.LiveView unquote(html_helpers()) end end diff --git a/lib/mailgun_logger_web/channels/user_socket.ex b/lib/mailgun_logger_web/channels/user_socket.ex deleted file mode 100644 index 7a50df2..0000000 --- a/lib/mailgun_logger_web/channels/user_socket.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule MailgunLoggerWeb.UserSocket do - @moduledoc false - - use Phoenix.Socket - - ## Channels - # channel "room:*", MailgunLoggerWeb.RoomChannel - - ## Transports - - # Socket params are passed from the client and can - # be used to verify and authenticate a user. After - # verification, you can put default assigns into - # the socket that will be set for all channels, ie - # - # {:ok, assign(socket, :user_id, verified_user_id)} - # - # To deny connection, return `:error`. - # - # See `Phoenix.Token` documentation for examples in - # performing token verification on connect. - def connect(_params, socket) do - {:ok, socket} - end - - # Socket id's are topics that allow you to identify all sockets for a given user: - # - # def id(socket), do: "user_socket:#{socket.assigns.user_id}" - # - # Would allow you to broadcast a "disconnect" event and terminate - # all active sockets and channels for a given user: - # - # MailgunLoggerWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) - # - # Returning `nil` makes this socket anonymous. - def id(_socket), do: nil -end diff --git a/lib/mailgun_logger_web/components/core_components.ex b/lib/mailgun_logger_web/components/core_components.ex index 528b662..938c0b1 100644 --- a/lib/mailgun_logger_web/components/core_components.ex +++ b/lib/mailgun_logger_web/components/core_components.ex @@ -17,7 +17,7 @@ defmodule MailgunLoggerWeb.CoreComponents do use Phoenix.Component alias Phoenix.LiveView.JS - import MailgunLoggerWeb.Gettext + use Gettext, backend: MailgunLoggerWeb.Gettext @doc """ Renders a modal. @@ -280,8 +280,8 @@ defmodule MailgunLoggerWeb.CoreComponents do assign_new(assigns, :checked, fn -> Phoenix.HTML.Form.normalize_value("checkbox", value) end) ~H""" -
-