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
-[](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"""
-