Merge remote-tracking branch 'origin/main' into feature/372-groups-management
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon 2026-01-27 23:48:31 +01:00
commit 3eb4cde0b7
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
17 changed files with 330 additions and 38 deletions

View file

@ -2252,3 +2252,13 @@ msgid "Total: %{count} member"
msgid_plural "Total: %{count} members"
msgstr[0] "Insgesamt: %{count} Mitglied"
msgstr[1] "Insgesamt: %{count} Mitglieder"
#: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be edited."
msgstr "Dieser Benutzer kann nicht bearbeitet werden."
#: lib/mv_web/live/user_live/show.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be viewed."
msgstr "Dieser Benutzer kann nicht angezeigt werden."

View file

@ -2253,3 +2253,13 @@ msgid "Total: %{count} member"
msgid_plural "Total: %{count} members"
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be edited."
msgstr ""
#: lib/mv_web/live/user_live/show.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be viewed."
msgstr ""

View file

@ -2253,3 +2253,13 @@ msgid "Total: %{count} member"
msgid_plural "Total: %{count} members"
msgstr[0] ""
msgstr[1] ""
#: lib/mv_web/live/user_live/form.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be edited."
msgstr ""
#: lib/mv_web/live/user_live/show.ex
#, elixir-autogen, elixir-format
msgid "This user cannot be viewed."
msgstr ""

View file

@ -0,0 +1,62 @@
defmodule Mv.Repo.Migrations.EnsureSystemActorUserExists do
@moduledoc """
Ensures the system actor user always exists.
The system actor is used for systemic operations (email sync, cycle generation,
background jobs). It is created by seeds in development; in production seeds
may not run, so this migration guarantees the user exists.
Creates a user with email "system@mila.local" (default from Mv.Helpers.SystemActor)
and the Admin role. The user has no password and no OIDC ID, so it cannot log in.
"""
use Ecto.Migration
import Ecto.Query
@system_user_email "system@mila.local"
def up do
admin_role_id = ensure_admin_role_exists()
ensure_system_actor_user_exists(admin_role_id)
end
def down do
# Not reversible - do not delete system user on rollback
:ok
end
defp ensure_admin_role_exists do
case repo().one(from(r in "roles", where: r.name == "Admin", select: r.id)) do
nil ->
execute("""
INSERT INTO roles (id, name, description, permission_set_name, is_system_role, inserted_at, updated_at)
VALUES (uuid_generate_v7(), 'Admin', 'Administrator with full access', 'admin', false, (now() AT TIME ZONE 'utc'), (now() AT TIME ZONE 'utc'))
""")
role_id = repo().one(from(r in "roles", where: r.name == "Admin", select: r.id))
IO.puts("✅ Created 'Admin' role (was missing)")
role_id
role_id ->
role_id
end
end
defp ensure_system_actor_user_exists(_admin_role_id) do
case repo().one(from(u in "users", where: u.email == ^@system_user_email, select: u.id)) do
nil ->
# Use subquery for role_id to avoid nil/empty-string UUID (CI can lag after role insert)
execute("""
INSERT INTO users (id, email, hashed_password, oidc_id, member_id, role_id)
SELECT uuid_generate_v7(), '#{@system_user_email}', NULL, NULL, NULL, r.id
FROM roles r
WHERE r.name = 'Admin'
LIMIT 1
""")
IO.puts("✅ Created system actor user (#{@system_user_email})")
_ ->
:ok
end
end
end

View file

@ -268,9 +268,9 @@ case Accounts.User
|> 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
# Use authorize?: false for bootstrap; :update_internal bypasses system-user modification block
existing_system_user
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false)
@ -287,7 +287,7 @@ case Accounts.User
upsert_identity: :unique_email,
authorize?: false
)
|> Ash.Changeset.for_update(:update, %{})
|> Ash.Changeset.for_update(:update_internal, %{})
|> Ash.Changeset.manage_relationship(:role, admin_role, type: :append_and_remove)
|> Ash.update!(authorize?: false)