63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
"""Nutzer, Teams und Web-Sitzungen (→ Konzept 5)."""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import Boolean, ForeignKey, Integer, String, false, true
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.database import Base, now
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
username: Mapped[str] = mapped_column(String(64), unique=True)
|
|
display_name: Mapped[str] = mapped_column(String(200))
|
|
# Globale Administration (Modelle, Connectoren, Policies)
|
|
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
# Darf Anträge mit Freigabestufe global_approver entscheiden, auch ohne Mitglied des Workspace zu sein
|
|
is_global_approver: Mapped[bool] = mapped_column(Boolean, default=False, server_default=false())
|
|
# Für Benachrichtigungen; notify = Nutzer möchte E-Mails erhalten
|
|
email: Mapped[str | None] = mapped_column(String(254))
|
|
notify: Mapped[bool] = mapped_column(Boolean, default=True, server_default=true())
|
|
# Kennung aus dem späteren Identity Provider (OIDC/Keycloak); leer bei Demo-Nutzern
|
|
external_id: Mapped[str | None] = mapped_column(String(200), unique=True)
|
|
active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
# Eigenes Passwort (Argon2id); leer = nur Demo-Anmeldung bzw. später Keycloak
|
|
password_hash: Mapped[str | None] = mapped_column(String(200))
|
|
# Nach Anlegen oder Zurücksetzen: erst ein eigenes Passwort setzen
|
|
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
failed_logins: Mapped[int] = mapped_column(Integer, default=0)
|
|
locked_until: Mapped[datetime | None]
|
|
password_changed: Mapped[datetime | None]
|
|
created: Mapped[datetime] = mapped_column(default=now)
|
|
|
|
|
|
class Team(Base):
|
|
__tablename__ = "teams"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
slug: Mapped[str] = mapped_column(String(64), unique=True)
|
|
name: Mapped[str] = mapped_column(String(200))
|
|
created: Mapped[datetime] = mapped_column(default=now)
|
|
|
|
|
|
class TeamMembership(Base):
|
|
__tablename__ = "team_memberships"
|
|
|
|
team_id: Mapped[int] = mapped_column(ForeignKey("teams.id", ondelete="CASCADE"), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
|
|
|
|
|
|
class WebSession(Base):
|
|
"""Serverseitige Sitzung; im Cookie steht nur das Token, hier sein SHA-256."""
|
|
|
|
__tablename__ = "web_sessions"
|
|
|
|
id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
|
|
csrf: Mapped[str] = mapped_column(String(64))
|
|
created: Mapped[datetime] = mapped_column(default=now)
|
|
expires_at: Mapped[datetime]
|
|
revoked_at: Mapped[datetime | None]
|