mitgliederverwaltung/lib/membership/join_request.ex
Simon e7393e32d8
Some checks failed
continuous-integration/drone/push Build is failing
feat: join request backend
2026-02-20 17:50:51 +01:00

141 lines
3.2 KiB
Elixir

defmodule Mv.Membership.JoinRequest do
@moduledoc """
Ash resource for public join requests (onboarding flow).
Created only after email confirmation (double opt-in). Per concept §2.3.2:
- email (dedicated field), payload, schema_version, status, submitted_at, source
- approved_at, rejected_at, reviewed_by_user_id for audit (Step 2)
- confirmation_token_hash for idempotency (unique constraint)
"""
use Ash.Resource,
domain: Mv.Membership,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
postgres do
table "join_requests"
repo Mv.Repo
end
actions do
defaults [:read, :destroy]
create :create do
primary? true
accept [
:email,
:confirmation_token_hash,
:status,
:submitted_at,
:source,
:schema_version,
:payload,
:approved_at,
:rejected_at,
:reviewed_by_user_id
]
end
create :confirm do
description "Public action: create JoinRequest after confirmation link click (actor: nil)"
accept [
:email,
:confirmation_token_hash,
:status,
:submitted_at,
:source,
:schema_version,
:payload
]
end
update :update do
accept [:status, :approved_at, :rejected_at, :reviewed_by_user_id]
require_atomic? false
end
end
policies do
policy action(:confirm) do
description "Allow public confirmation (actor nil) for join flow"
authorize_if Ash.Policy.Check.Builtins.actor_absent()
end
policy action_type(:read) do
description "Allow read when actor nil (success page) or when user has permission"
authorize_if Ash.Policy.Check.Builtins.actor_absent()
authorize_if Mv.Authorization.Checks.HasPermission
end
policy action(:create) do
description "Generic create only for authorized users; public uses :confirm"
authorize_if Mv.Authorization.Checks.HasPermission
end
policy action_type([:update, :destroy]) do
authorize_if Mv.Authorization.Checks.HasPermission
end
end
attributes do
uuid_v7_primary_key :id
attribute :email, :string do
allow_nil? false
public? true
end
attribute :confirmation_token_hash, :string do
allow_nil? false
public? true
end
attribute :status, :string do
allow_nil? false
public? true
default "submitted"
end
attribute :submitted_at, :utc_datetime_usec do
allow_nil? false
public? true
end
attribute :source, :string do
allow_nil? false
public? true
end
attribute :schema_version, :integer do
allow_nil? false
public? true
end
attribute :payload, :map do
allow_nil? true
public? true
default %{}
end
attribute :approved_at, :utc_datetime_usec do
allow_nil? true
public? true
end
attribute :rejected_at, :utc_datetime_usec do
allow_nil? true
public? true
end
attribute :reviewed_by_user_id, :uuid do
allow_nil? true
public? true
end
timestamps()
end
identities do
identity :unique_confirmation_token_hash, [:confirmation_token_hash]
end
end