feat: add join confirmation and mail templating
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon 2026-03-09 18:15:12 +01:00
parent 3672ef0d03
commit 6385fbc831
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
24 changed files with 585 additions and 53 deletions

View file

@ -30,6 +30,7 @@ defmodule Mv.Membership do
import Ash.Expr
alias Ash.Error.Query.NotFound, as: NotFoundError
alias Mv.Membership.JoinRequest
alias MvWeb.Emails.JoinConfirmationEmail
admin do
show? true
@ -85,7 +86,7 @@ defmodule Mv.Membership do
end
resource Mv.Membership.JoinRequest do
define :submit_join_request, action: :submit
# submit_join_request/2 implemented as custom function below (create + send email)
end
end
@ -350,6 +351,47 @@ defmodule Mv.Membership do
|> then(&Ash.read_one(query, &1))
end
@doc """
Creates a join request (submit flow) and sends the confirmation email.
Generates a confirmation token if not provided in attrs (e.g. for tests, pass
`:confirmation_token` to get a known token). On success, sends one email with
the confirm link to the request email.
## Options
- `:actor` - Must be nil for public submit (policy allows only unauthenticated).
## Returns
- `{:ok, request}` - Created JoinRequest in status pending_confirmation
- `{:error, error}` - Validation or authorization error
"""
def submit_join_request(attrs, opts \\ []) do
actor = Keyword.get(opts, :actor)
token = Map.get(attrs, :confirmation_token) || generate_confirmation_token()
attrs_with_token =
attrs |> Map.drop([:confirmation_token]) |> Map.put(:confirmation_token, token)
case Ash.create(JoinRequest, attrs_with_token,
action: :submit,
actor: actor,
domain: __MODULE__
) do
{:ok, request} ->
JoinConfirmationEmail.send(request.email, token)
{:ok, request}
error ->
error
end
end
defp generate_confirmation_token do
32
|> :crypto.strong_rand_bytes()
|> Base.url_encode64(padding: false)
end
@doc """
Confirms a join request by token (public confirmation link).