Implemented comprehensive email invitation functionality for new users. Added SMTP settings management in admin panel with configuration UI for host, port, credentials, sender details, and TLS options. Created invite token system with 48-hour expiration and single-use enforcement. Added mailer module with HTML email templates in German theme styling (radial gradient backgrounds, gold accents, bordered cards). Implemented invite
126 lines
4.9 KiB
Python
126 lines
4.9 KiB
Python
# backend/app/models.py
|
|
import enum
|
|
import uuid
|
|
from sqlalchemy import (
|
|
String,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
SmallInteger,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from sqlalchemy.sql import func
|
|
from .db import Base
|
|
|
|
|
|
class Role(str, enum.Enum):
|
|
admin = "admin"
|
|
user = "user"
|
|
|
|
|
|
class Category(str, enum.Enum):
|
|
suspect = "suspect"
|
|
item = "item"
|
|
location = "location"
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
email: Mapped[str] = mapped_column(String, unique=True, index=True)
|
|
password_hash: Mapped[str] = mapped_column(String)
|
|
role: Mapped[str] = mapped_column(String, default=Role.user.value)
|
|
disabled: Mapped[bool] = mapped_column(Boolean, default=False)
|
|
created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
theme_key: Mapped[str] = mapped_column(String, default="default")
|
|
|
|
# NEW: schöner Name für UI (TopBar / WinnerBadge)
|
|
display_name: Mapped[str] = mapped_column(String, default="")
|
|
|
|
|
|
class AppSettings(Base):
|
|
__tablename__ = "app_settings"
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default="default")
|
|
smtp_host: Mapped[str] = mapped_column(String, default="")
|
|
smtp_port: Mapped[int] = mapped_column(Integer, default=587)
|
|
smtp_username: Mapped[str] = mapped_column(String, default="")
|
|
smtp_password: Mapped[str] = mapped_column(String, default="")
|
|
smtp_from_email: Mapped[str] = mapped_column(String, default="")
|
|
smtp_from_name: Mapped[str] = mapped_column(String, default="Cluedo HP")
|
|
smtp_use_tls: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
app_base_url: Mapped[str] = mapped_column(String, default="http://localhost:8081")
|
|
|
|
|
|
class InviteToken(Base):
|
|
__tablename__ = "invite_tokens"
|
|
__table_args__ = (UniqueConstraint("token_hash", name="uq_invite_token_hash"),)
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
|
|
token_hash: Mapped[str] = mapped_column(String, index=True)
|
|
expires_at: Mapped[str] = mapped_column(DateTime(timezone=True))
|
|
used_at: Mapped[str | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class Game(Base):
|
|
__tablename__ = "games"
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
|
|
host_user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
|
|
|
|
name: Mapped[str] = mapped_column(String)
|
|
seed: Mapped[int] = mapped_column(Integer)
|
|
created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
code: Mapped[str] = mapped_column(String, unique=True, index=True)
|
|
|
|
winner_user_id: Mapped[str | None] = mapped_column(String, ForeignKey("users.id"), nullable=True)
|
|
started_at: Mapped[str | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
|
|
class GameChip(Base):
|
|
__tablename__ = "game_chips"
|
|
__table_args__ = (UniqueConstraint("game_id", "user_id", name="uq_game_chip_user"),)
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
game_id: Mapped[str] = mapped_column(String, ForeignKey("games.id"), index=True)
|
|
user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
|
|
chip: Mapped[str] = mapped_column(String)
|
|
|
|
|
|
class GameMember(Base):
|
|
__tablename__ = "game_members"
|
|
__table_args__ = (UniqueConstraint("game_id", "user_id", name="uq_game_member"),)
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
game_id: Mapped[str] = mapped_column(String, ForeignKey("games.id"), index=True)
|
|
user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
|
|
joined_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
|
|
class Entry(Base):
|
|
__tablename__ = "entries"
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
category: Mapped[str] = mapped_column(String, index=True)
|
|
label: Mapped[str] = mapped_column(String)
|
|
|
|
|
|
class SheetState(Base):
|
|
__tablename__ = "sheet_state"
|
|
__table_args__ = (UniqueConstraint("game_id", "owner_user_id", "entry_id", name="uq_sheet"),)
|
|
|
|
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
game_id: Mapped[str] = mapped_column(String, ForeignKey("games.id"), index=True)
|
|
owner_user_id: Mapped[str] = mapped_column(String, ForeignKey("users.id"), index=True)
|
|
entry_id: Mapped[str] = mapped_column(String, ForeignKey("entries.id"), index=True)
|
|
|
|
status: Mapped[int] = mapped_column(SmallInteger, default=0)
|
|
note_tag: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
|
|
chip: Mapped[str | None] = mapped_column(String, nullable=True)
|
|
|