| """Idempotently seed a full admin login account into PostgreSQL. |
| |
| Unlike scripts/seed_admin.py (which only *promotes* an already-registered user), |
| this creates the account if it does not exist yet, then ensures it has the ADMIN |
| role. Safe to run repeatedly: |
| |
| python scripts/seed_admin_user.py [email] [password] |
| # or via env: |
| SEED_ADMIN_EMAIL=admin@metarec.local SEED_ADMIN_PASSWORD=Admin12345! \ |
| python scripts/seed_admin_user.py |
| |
| Behavior: |
| - If no user with the email exists -> register it, then set role=admin. |
| - If the user already exists -> leave the password untouched, just |
| (re)ensure role=admin. |
| |
| Requires DATABASE_URL to be set. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import asyncio |
| import os |
| import sys |
| from pathlib import Path |
|
|
| |
| |
| |
| _BACKEND_DIR = Path(__file__).resolve().parents[1] |
| if str(_BACKEND_DIR) not in sys.path: |
| sys.path.insert(0, str(_BACKEND_DIR)) |
|
|
| from business_models import UserRole |
| from business_repositories import auth_repository |
| from business_db import dispose_async_engine |
|
|
| DEFAULT_EMAIL = "admin@metarec.local" |
| DEFAULT_PASSWORD = "Admin12345!" |
|
|
|
|
| def _credentials() -> tuple[str, str]: |
| email = (sys.argv[1] if len(sys.argv) > 1 else os.getenv("SEED_ADMIN_EMAIL") or DEFAULT_EMAIL).strip() |
| password = sys.argv[2] if len(sys.argv) > 2 else os.getenv("SEED_ADMIN_PASSWORD") or DEFAULT_PASSWORD |
| return email, password |
|
|
|
|
| async def _seed(email: str, password: str) -> str: |
| """Returns one of: 'created', 'exists'. Always ensures ADMIN role.""" |
| try: |
| try: |
| await auth_repository.register(email=email, password=password, display_name="Admin") |
| outcome = "created" |
| except ValueError as exc: |
| if "already registered" not in str(exc): |
| raise |
| outcome = "exists" |
| |
| if not await auth_repository.set_role_by_email(email, UserRole.ADMIN): |
| raise RuntimeError(f"could not set admin role for {email} (user not found after seed)") |
| return outcome |
| finally: |
| await dispose_async_engine() |
|
|
|
|
| def main() -> None: |
| if not os.getenv("DATABASE_URL"): |
| print("DATABASE_URL is required", file=sys.stderr) |
| raise SystemExit(2) |
|
|
| email, password = _credentials() |
| if len(password) < 8: |
| print("password must be at least 8 characters", file=sys.stderr) |
| raise SystemExit(2) |
|
|
| outcome = asyncio.run(_seed(email, password)) |
| if outcome == "created": |
| print(f"Seeded new ADMIN account: {email} (password set as provided)") |
| else: |
| print(f"Account {email} already existed; ensured ADMIN role (password left unchanged)") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|