vinindkoeb/scripts/create_admin_user.py
carsten e337c4a49e Tilføj produktions-installationsvejledning + bootstrap-script
INSTALL.md dækker hele vejen fra en frisk server til en kørende
løsning: forudsætninger, database, .env, migrations, admin-UI-build,
en systemd-unit til backend'en, domæne-/reverse proxy-krav (holdt
værktøjs-uafhængigt, med en kort CloudPanel-note), DNS, Postal-webhook,
rute-opsætning, verifikation og fremtidige opdateringer.

scripts/create_admin_user.py lukker en reel mangel: der findes ingen
selvbetjenings-registrering, så uden en bruger i databasen kan ingen
logge ind på en frisk installation. Scriptet opretter (eller genbruger)
en organisation + rute, samt en ny superadmin-bruger med korrekt
hashet adgangskode — samme mønster som det eksisterende
scripts/import_participants.py. Verificeret i en isoleret test
(oprettet bruger kunne logge ind via POST /auth/login), testdata
ryddet op igen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 10:17:36 +02:00

83 lines
3.3 KiB
Python

"""One-off bootstrap: opretter den første organisation + rute + superadmin-bruger.
Der findes ingen selvbetjenings-registrering i systemet — uden en
bruger i databasen kan ingen logge ind overhovedet. Dette script
dækker det på en frisk installation.
Genbruger en organisation/rute med samme navn, hvis de allerede
findes (sikkert at køre flere gange, fx for at oprette endnu en
admin-bruger på en eksisterende organisation). Fejler tydeligt hvis
en bruger med samme email allerede findes, i stedet for at duplikere.
Brug:
uv run python scripts/create_admin_user.py \
--org "Fælles Vinindkøb" --route "Fælles Vinindkøb" \
--email admin@example.com --name "Dit Navn" --password "..."
"""
import argparse
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from sqlmodel import Session, select
from app.core.security import hash_password
from app.db import engine
from app.models.organization import Organization
from app.models.route import Route
from app.models.user import User
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--org", required=True, help="Organisationens navn (genbruges hvis den allerede findes)")
parser.add_argument("--route", required=True, help="Rutens navn (genbruges hvis den allerede findes)")
parser.add_argument("--email", required=True, help="Email til den nye admin-bruger")
parser.add_argument("--name", required=True, help="Navn på den nye admin-bruger")
parser.add_argument("--password", required=True, help="Adgangskode til den nye admin-bruger")
args = parser.parse_args()
with Session(engine) as session:
existing_user = session.exec(select(User).where(User.email == args.email)).first()
if existing_user is not None:
print(f"Fejl: en bruger med email {args.email!r} findes allerede (id={existing_user.id}).")
sys.exit(1)
organization = session.exec(select(Organization).where(Organization.name == args.org)).first()
if organization is None:
organization = Organization(name=args.org)
session.add(organization)
session.flush()
print(f"Oprettede organisation {args.org!r} (id={organization.id}).")
else:
print(f"Genbruger eksisterende organisation {args.org!r} (id={organization.id}).")
route = session.exec(
select(Route).where(Route.name == args.route, Route.organization_id == organization.id)
).first()
if route is None:
route = Route(name=args.route, organization_id=organization.id)
session.add(route)
session.flush()
print(f"Oprettede rute {args.route!r} (id={route.id}).")
else:
print(f"Genbruger eksisterende rute {args.route!r} (id={route.id}).")
user = User(
email=args.email,
name=args.name,
hashed_password=hash_password(args.password),
is_active=True,
is_superadmin=True,
organization_id=organization.id,
)
session.add(user)
session.commit()
session.refresh(user)
print(f"Oprettede superadmin-bruger {args.email!r} (id={user.id}).")
if __name__ == "__main__":
main()