fix: create system account for service user to satisfy account_id NOT NULL on prod (#91)
The prod users table has account_id NOT NULL. The backfill migration and ensure_service_account() now create a 'ResolutionFlow System' account (display_code RF-SYS-1) before inserting the service user, satisfying the constraint on all environments. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #91.
This commit is contained in:
@@ -13,7 +13,6 @@ from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -25,18 +24,40 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
SERVICE_ACCOUNT_EMAIL = "noreply@resolutionflow.com"
|
||||
SERVICE_ACCOUNT_NAME = "ResolutionFlow"
|
||||
SYSTEM_ACCOUNT_NAME = "ResolutionFlow System"
|
||||
SYSTEM_ACCOUNT_DISPLAY_CODE = "RF-SYS-1"
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
conn = op.get_bind()
|
||||
|
||||
# Ensure service account exists
|
||||
# Check if service user already exists
|
||||
row = conn.execute(
|
||||
sa.text("SELECT id FROM users WHERE email = :email"),
|
||||
sa.text("SELECT id, account_id FROM users WHERE email = :email"),
|
||||
{"email": SERVICE_ACCOUNT_EMAIL},
|
||||
).fetchone()
|
||||
|
||||
if row is None:
|
||||
if row is not None:
|
||||
service_id = str(row[0])
|
||||
else:
|
||||
# Ensure a system account exists for the service user
|
||||
acct_row = conn.execute(
|
||||
sa.text("SELECT id FROM accounts WHERE display_code = :code"),
|
||||
{"code": SYSTEM_ACCOUNT_DISPLAY_CODE},
|
||||
).fetchone()
|
||||
|
||||
if acct_row is None:
|
||||
account_id = str(uuid.uuid4())
|
||||
conn.execute(
|
||||
sa.text("""
|
||||
INSERT INTO accounts (id, name, display_code, created_at, updated_at)
|
||||
VALUES (:id, :name, :code, NOW(), NOW())
|
||||
"""),
|
||||
{"id": account_id, "name": SYSTEM_ACCOUNT_NAME, "code": SYSTEM_ACCOUNT_DISPLAY_CODE},
|
||||
)
|
||||
else:
|
||||
account_id = str(acct_row[0])
|
||||
|
||||
service_id = str(uuid.uuid4())
|
||||
conn.execute(
|
||||
sa.text("""
|
||||
@@ -44,12 +65,12 @@ def upgrade() -> None:
|
||||
id, email, name, password_hash, role,
|
||||
is_super_admin, is_team_admin, is_active,
|
||||
is_service_account, must_change_password,
|
||||
account_role, created_at
|
||||
account_id, account_role, created_at
|
||||
) VALUES (
|
||||
:id, :email, :name, :password_hash, 'engineer',
|
||||
false, false, true,
|
||||
true, false,
|
||||
'engineer', NOW()
|
||||
:account_id, 'engineer', NOW()
|
||||
)
|
||||
"""),
|
||||
{
|
||||
@@ -57,10 +78,9 @@ def upgrade() -> None:
|
||||
"email": SERVICE_ACCOUNT_EMAIL,
|
||||
"name": SERVICE_ACCOUNT_NAME,
|
||||
"password_hash": "!service-account-no-login",
|
||||
"account_id": account_id,
|
||||
},
|
||||
)
|
||||
else:
|
||||
service_id = str(row[0])
|
||||
|
||||
# Backfill is_default trees that have no author
|
||||
result = conn.execute(
|
||||
@@ -75,7 +95,6 @@ def upgrade() -> None:
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Restore NULL on trees that were authored by the service account and are default
|
||||
conn = op.get_bind()
|
||||
row = conn.execute(
|
||||
sa.text("SELECT id FROM users WHERE email = :email"),
|
||||
|
||||
Reference in New Issue
Block a user