Ensure system actor user exists via migration

Creates user system@mila.local with Admin role if missing. Idempotent;
guarantees system actor in production without relying on seeds.
This commit is contained in:
Moritz 2026-01-27 14:29:01 +01:00 committed by moritz
parent 0a2aa3bad0
commit acb33b9f3b

View file

@ -0,0 +1,59 @@
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 ->
execute("""
INSERT INTO users (id, email, hashed_password, oidc_id, member_id, role_id)
SELECT gen_random_uuid(), '#{@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