Merge branch 'main' into feature/filter-boolean-custom-fields
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Simon 2026-01-23 14:41:48 +01:00
commit 672b4a8250
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
45 changed files with 3166 additions and 359 deletions

View file

@ -1945,12 +1945,7 @@ msgstr "Bezahlstatus"
msgid "Reset"
msgstr "Zurücksetzen"
#~ #: lib/mv_web/live/components/member_filter_component.ex
#~ #, elixir-autogen, elixir-format
#~ msgid "Filter by %{name}"
#~ msgstr "Filtern nach %{name}"
#~ #: lib/mv_web/live/components/member_filter_component.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Payment status filter"
#~ msgstr "Bezahlstatusfilter"
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Only administrators can regenerate cycles"
msgstr "Nur Administrator*innen können Zyklen regenerieren"

View file

@ -1945,3 +1945,8 @@ msgstr ""
#, elixir-autogen, elixir-format
msgid "Reset"
msgstr ""
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Only administrators can regenerate cycles"
msgstr ""

View file

@ -1946,6 +1946,11 @@ msgstr ""
msgid "Reset"
msgstr ""
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Only administrators can regenerate cycles"
msgstr ""
#~ #: lib/mv_web/live/custom_field_value_live/form.ex
#~ #, elixir-autogen, elixir-format, fuzzy
#~ msgid "Use this form to manage Custom Field Value records in your database."

View file

@ -161,24 +161,30 @@ end
# This handles both existing users (e.g., from OIDC) and newly created users
case Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts) do
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
{:ok, existing_admin_user} when not is_nil(existing_admin_user) ->
# User already exists (e.g., via OIDC) - assign admin role
# Use authorize?: false for bootstrap - this is initial setup
existing_admin_user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.update!(authorize?: false)
{:ok, nil} ->
# User doesn't exist - create admin user with password
Accounts.create_user!(%{email: admin_email}, upsert?: true, upsert_identity: :unique_email)
# Use authorize?: false for bootstrap - no admin user exists yet to use as actor
Accounts.create_user!(%{email: admin_email},
upsert?: true,
upsert_identity: :unique_email,
authorize?: false
)
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|> Ash.update!()
|> Ash.update!(authorize?: false)
|> then(fn user ->
user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!()
|> Ash.update!(authorize?: false)
end)
{:error, error} ->
@ -190,10 +196,10 @@ end
admin_user_with_role =
case Accounts.User
|> Ash.Query.filter(email == ^admin_email)
|> Ash.read_one(domain: Mv.Accounts) do
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
{:ok, user} when not is_nil(user) ->
user
|> Ash.load!(:role)
|> Ash.load!(:role, authorize?: false)
{:ok, nil} ->
raise "Admin user not found after creation/assignment"
@ -202,6 +208,45 @@ admin_user_with_role =
raise "Failed to load admin user: #{inspect(error)}"
end
# Create system user for systemic operations (email sync, validations, cycle generation)
# This user is used by Mv.Helpers.SystemActor for operations that must always run
# Email is configurable via SYSTEM_ACTOR_EMAIL environment variable
system_user_email = Mv.Helpers.SystemActor.system_user_email()
case Accounts.User
|> Ash.Query.filter(email == ^system_user_email)
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
{:ok, existing_system_user} when not is_nil(existing_system_user) ->
# System user already exists - ensure it has admin role
# Use authorize?: false for bootstrap
existing_system_user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false)
{:ok, nil} ->
# System user doesn't exist - create it with admin role
# SECURITY: System user must NOT be able to log in:
# - No password (hashed_password = nil) - prevents password login
# - No OIDC ID (oidc_id = nil) - prevents OIDC login
# - This user is ONLY for internal system operations via SystemActor
# If either hashed_password or oidc_id is set, the user could potentially log in
# Use authorize?: false for bootstrap - system user creation happens before system actor exists
Accounts.create_user!(%{email: system_user_email},
upsert?: true,
upsert_identity: :unique_email,
authorize?: false
)
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false)
{:error, error} ->
# Log error but don't fail seeds - SystemActor will fall back to admin user
IO.puts("Warning: Failed to create system user: #{inspect(error)}")
IO.puts("SystemActor will fall back to admin user (#{admin_email})")
end
# Load all membership fee types for assignment
# Sort by name to ensure deterministic order
all_fee_types =
@ -361,9 +406,20 @@ additional_users = [
created_users =
Enum.map(additional_users, fn user_attrs ->
Accounts.create_user!(user_attrs, upsert?: true, upsert_identity: :unique_email)
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|> Ash.update!()
# Use admin user as actor for additional user creation (not bootstrap)
user =
Accounts.create_user!(user_attrs,
upsert?: true,
upsert_identity: :unique_email,
actor: admin_user_with_role
)
|> Ash.Changeset.for_update(:admin_set_password, %{password: "testpassword"})
|> Ash.update!(actor: admin_user_with_role)
# Reload user to ensure all fields (including member_id) are loaded
Accounts.User
|> Ash.Query.filter(id == ^user.id)
|> Ash.read_one!(domain: Mv.Accounts, actor: admin_user_with_role)
end)
# Create members with linked users to demonstrate the 1:1 relationship
@ -413,11 +469,13 @@ Enum.with_index(linked_members)
member =
if user.member_id == nil do
# User is free, create member and link - use upsert to prevent duplicates
# Use authorize?: false for User lookup during relationship management (bootstrap phase)
Membership.create_member!(
Map.put(member_attrs_without_fee_type, :user, %{id: user.id}),
upsert?: true,
upsert_identity: :unique_email,
actor: admin_user_with_role
actor: admin_user_with_role,
authorize?: false
)
else
# User already has a member, just create the member without linking - use upsert to prevent duplicates