Split seeds into bootstrap and dev-only
This commit is contained in:
parent
0b23b816fb
commit
edd8657c92
5 changed files with 824 additions and 805 deletions
313
priv/repo/seeds_bootstrap.exs
Normal file
313
priv/repo/seeds_bootstrap.exs
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
# Bootstrap seeds: run in all environments (dev, test, prod).
|
||||
# Creates only data required for system startup: fee types, custom fields,
|
||||
# roles, admin user, system user, global settings. No members, no groups.
|
||||
|
||||
alias Mv.Accounts
|
||||
alias Mv.Membership
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
|
||||
require Ash.Query
|
||||
|
||||
# 1. Membership fee types (authorize?: false for bootstrap)
|
||||
# Names without interval to avoid duplication in UI; interval is shown separately.
|
||||
fee_type_configs = [
|
||||
%{
|
||||
name: "Standard",
|
||||
amount: Decimal.new("120.00"),
|
||||
interval: :yearly,
|
||||
description: "Standard jährlicher Mitgliedsbeitrag"
|
||||
},
|
||||
%{
|
||||
name: "Ermäßigt",
|
||||
amount: Decimal.new("80.00"),
|
||||
interval: :yearly,
|
||||
description: "Ermäßigter jährlicher Mitgliedsbeitrag"
|
||||
},
|
||||
%{
|
||||
name: "Unterstützer",
|
||||
amount: Decimal.new("60.00"),
|
||||
interval: :half_yearly,
|
||||
description: "Unterstützerbeitrag halbjährlich"
|
||||
},
|
||||
%{
|
||||
name: "Fördermitglied",
|
||||
amount: Decimal.new("30.00"),
|
||||
interval: :quarterly,
|
||||
description: "Fördermitgliedschaft quartalsweise"
|
||||
},
|
||||
%{
|
||||
name: "Probemitgliedschaft",
|
||||
amount: Decimal.new("10.00"),
|
||||
interval: :monthly,
|
||||
description: "Probemitgliedschaft monatlich"
|
||||
}
|
||||
]
|
||||
|
||||
for attrs <- fee_type_configs do
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!(
|
||||
upsert?: true,
|
||||
upsert_identity: :unique_name,
|
||||
authorize?: false,
|
||||
domain: Mv.MembershipFees
|
||||
)
|
||||
end
|
||||
|
||||
# Resolve default fee type (Standard, 120€ yearly) for settings
|
||||
default_fee_type =
|
||||
Mv.MembershipFees.MembershipFeeType
|
||||
|> Ash.Query.filter(name == "Standard")
|
||||
|> Ash.read_one!(authorize?: false, domain: Mv.MembershipFees)
|
||||
|
||||
# 2. Custom fields (authorize?: false for bootstrap)
|
||||
# Only Geburtsdatum is shown in overview by default; others hidden to avoid clutter.
|
||||
custom_field_configs = [
|
||||
%{
|
||||
name: "Geburtsdatum",
|
||||
value_type: :date,
|
||||
description: "Geburtsdatum der/des Mitglieds",
|
||||
required: false,
|
||||
show_in_overview: true
|
||||
},
|
||||
%{
|
||||
name: "Datenschutzerklärung akzeptiert",
|
||||
value_type: :boolean,
|
||||
description: "Angabe, ob Datenschutzerklärung akzeptiert wurde",
|
||||
required: false,
|
||||
show_in_overview: false
|
||||
},
|
||||
%{
|
||||
name: "SEPA-Mandat",
|
||||
value_type: :boolean,
|
||||
description: "SEPA-Lastschriftmandat erteilt",
|
||||
required: false,
|
||||
show_in_overview: false
|
||||
},
|
||||
%{
|
||||
name: "Rechnungs-E-Mail",
|
||||
value_type: :email,
|
||||
description: "E-Mail-Adresse für Rechnungen",
|
||||
required: false,
|
||||
show_in_overview: false
|
||||
},
|
||||
%{
|
||||
name: "IBAN",
|
||||
value_type: :string,
|
||||
description: "IBAN für Lastschrift",
|
||||
required: false,
|
||||
show_in_overview: false
|
||||
},
|
||||
%{
|
||||
name: "Stunden ehrenamtlich",
|
||||
value_type: :integer,
|
||||
description: "Geleistete ehrenamtliche Stunden",
|
||||
required: false,
|
||||
show_in_overview: false
|
||||
}
|
||||
]
|
||||
|
||||
for attrs <- custom_field_configs do
|
||||
Membership.create_custom_field!(
|
||||
attrs,
|
||||
upsert?: true,
|
||||
upsert_identity: :unique_name,
|
||||
authorize?: false
|
||||
)
|
||||
end
|
||||
|
||||
# 3. Admin email and password fallback for dev/test
|
||||
admin_email = System.get_env("ADMIN_EMAIL") || "admin@localhost"
|
||||
System.put_env("ADMIN_EMAIL", admin_email)
|
||||
|
||||
if Mix.env() in [:dev, :test] and is_nil(System.get_env("ADMIN_PASSWORD")) and
|
||||
is_nil(System.get_env("ADMIN_PASSWORD_FILE")) do
|
||||
System.put_env("ADMIN_PASSWORD", "testpassword")
|
||||
end
|
||||
|
||||
# 4. Authorization roles (German descriptions)
|
||||
role_configs = [
|
||||
%{
|
||||
name: "Mitglied",
|
||||
description: "Standardrolle für Mitglieder mit Zugriff nur auf die eigenen Daten",
|
||||
permission_set_name: "own_data",
|
||||
is_system_role: true
|
||||
},
|
||||
%{
|
||||
name: "Vorstand",
|
||||
description: "Vorstandsmitglied mit Lesezugriff auf alle Mitgliederdaten",
|
||||
permission_set_name: "read_only",
|
||||
is_system_role: false
|
||||
},
|
||||
%{
|
||||
name: "Kassenwart",
|
||||
description: "Kassenwart mit voller Mitglieder- und Zahlungsverwaltung",
|
||||
permission_set_name: "normal_user",
|
||||
is_system_role: false
|
||||
},
|
||||
%{
|
||||
name: "Buchhaltung",
|
||||
description: "Buchhaltung mit Lesezugriff für Prüfungen",
|
||||
permission_set_name: "read_only",
|
||||
is_system_role: false
|
||||
},
|
||||
%{
|
||||
name: "Admin",
|
||||
description: "Administrator mit uneingeschränktem Zugriff",
|
||||
permission_set_name: "admin",
|
||||
is_system_role: false
|
||||
}
|
||||
]
|
||||
|
||||
Enum.each(role_configs, fn role_data ->
|
||||
role_name = role_data.name
|
||||
|
||||
case Mv.Authorization.Role
|
||||
|> Ash.Query.filter(name == ^role_name)
|
||||
|> Ash.read_one(authorize?: false, domain: Mv.Authorization) do
|
||||
{:ok, existing_role} when not is_nil(existing_role) ->
|
||||
if existing_role.permission_set_name != role_data.permission_set_name or
|
||||
existing_role.description != role_data.description do
|
||||
existing_role
|
||||
|> Ash.Changeset.for_update(:update_role, %{
|
||||
description: role_data.description,
|
||||
permission_set_name: role_data.permission_set_name
|
||||
})
|
||||
|> Ash.update!(authorize?: false, domain: Mv.Authorization)
|
||||
end
|
||||
|
||||
{:ok, nil} ->
|
||||
Mv.Authorization.Role
|
||||
|> Ash.Changeset.for_create(:create_role_with_system_flag, role_data)
|
||||
|> Ash.create!(authorize?: false, domain: Mv.Authorization)
|
||||
|
||||
{:error, error} ->
|
||||
IO.puts("Warning: Failed to check for role #{role_data.name}: #{inspect(error)}")
|
||||
end
|
||||
end)
|
||||
|
||||
admin_role =
|
||||
case Mv.Authorization.Role
|
||||
|> Ash.Query.filter(name == "Admin")
|
||||
|> Ash.read_one(authorize?: false, domain: Mv.Authorization) do
|
||||
{:ok, role} when not is_nil(role) -> role
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
if is_nil(admin_role) do
|
||||
raise "Failed to create or find admin role. Cannot proceed with bootstrap."
|
||||
end
|
||||
|
||||
# 5. Admin user
|
||||
Mv.Release.seed_admin()
|
||||
|
||||
admin_user_with_role =
|
||||
case Accounts.User
|
||||
|> Ash.Query.filter(email == ^admin_email)
|
||||
|> Ash.read_one(domain: Mv.Accounts, authorize?: false) do
|
||||
{:ok, user} when not is_nil(user) ->
|
||||
user |> Ash.load!(:role, authorize?: false)
|
||||
|
||||
{:ok, nil} ->
|
||||
raise "Admin user not found after creation/assignment"
|
||||
|
||||
{:error, error} ->
|
||||
raise "Failed to load admin user: #{inspect(error)}"
|
||||
end
|
||||
|
||||
# 6. System user
|
||||
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) ->
|
||||
existing_system_user
|
||||
|> Ash.Changeset.for_update(:update_internal, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update!(authorize?: false)
|
||||
|
||||
{:ok, nil} ->
|
||||
Accounts.create_user!(%{email: system_user_email},
|
||||
upsert?: true,
|
||||
upsert_identity: :unique_email,
|
||||
authorize?: false
|
||||
)
|
||||
|> Ash.Changeset.for_update(:update_internal, %{})
|
||||
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|
||||
|> Ash.update!(authorize?: false)
|
||||
|
||||
{:error, error} ->
|
||||
IO.puts("Warning: Failed to create system user: #{inspect(error)}")
|
||||
IO.puts("SystemActor will fall back to admin user (#{admin_email})")
|
||||
end
|
||||
|
||||
# 7. Global settings (with default membership fee type and default field visibility)
|
||||
# By default hide exit_date, notes, country, membership_fee_start_date in overview (like exit_date).
|
||||
default_club_name = System.get_env("ASSOCIATION_NAME") || "Club Name"
|
||||
|
||||
default_hidden_in_overview = %{
|
||||
"exit_date" => false,
|
||||
"notes" => false,
|
||||
"country" => false,
|
||||
"membership_fee_start_date" => false
|
||||
}
|
||||
|
||||
case Membership.get_settings() do
|
||||
{:ok, existing_settings} ->
|
||||
updates =
|
||||
%{}
|
||||
|> then(fn acc ->
|
||||
if existing_settings.club_name != default_club_name,
|
||||
do: Map.put(acc, :club_name, default_club_name),
|
||||
else: acc
|
||||
end)
|
||||
|> then(fn acc ->
|
||||
if existing_settings.default_membership_fee_type_id != default_fee_type.id,
|
||||
do: Map.put(acc, :default_membership_fee_type_id, default_fee_type.id),
|
||||
else: acc
|
||||
end)
|
||||
|> then(fn acc ->
|
||||
visibility_config = existing_settings.member_field_visibility || %{}
|
||||
# Ensure default-hidden fields are set if not already present (string or atom keys)
|
||||
has_key = fn vis, k ->
|
||||
try do
|
||||
Map.has_key?(vis, k) or Map.has_key?(vis, String.to_existing_atom(k))
|
||||
rescue
|
||||
ArgumentError -> Map.has_key?(vis, k)
|
||||
end
|
||||
end
|
||||
merged =
|
||||
Enum.reduce(default_hidden_in_overview, visibility_config, fn {key, val}, vis ->
|
||||
if has_key.(vis, key), do: vis, else: Map.put(vis, key, val)
|
||||
end)
|
||||
if merged != visibility_config, do: Map.put(acc, :member_field_visibility, merged), else: acc
|
||||
end)
|
||||
|
||||
if map_size(updates) > 0 do
|
||||
{:ok, _} = Membership.update_settings(existing_settings, updates)
|
||||
end
|
||||
|
||||
{:ok, nil} ->
|
||||
{:ok, _} =
|
||||
Membership.Setting
|
||||
|> Ash.Changeset.for_create(:create, %{
|
||||
club_name: default_club_name,
|
||||
member_field_visibility: default_hidden_in_overview,
|
||||
default_membership_fee_type_id: default_fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
end
|
||||
|
||||
IO.puts("✅ Bootstrap seeds completed.")
|
||||
|
||||
IO.puts(
|
||||
" - Fee types: 5 (Standard, Ermäßigt, Unterstützer, Fördermitglied, Probemitgliedschaft)"
|
||||
)
|
||||
|
||||
IO.puts(
|
||||
" - Custom fields: 6 (Geburtsdatum shown in overview; others hidden by default)"
|
||||
)
|
||||
|
||||
IO.puts(" - Roles: 5 (Mitglied, Vorstand, Kassenwart, Buchhaltung, Admin)")
|
||||
IO.puts(" - Default fee type: Standard (120€ yearly)")
|
||||
Loading…
Add table
Add a link
Reference in a new issue