left4me/l4d2web/models.py
mwiegand 0f825686c6
feat(live-state): add schema for snapshots, sessions, steam profiles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:18:24 +02:00

227 lines
10 KiB
Python

from datetime import UTC, datetime
from sqlalchemy import (
BigInteger,
Boolean,
DateTime,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
text,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
def now_utc() -> datetime:
return datetime.now(UTC)
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
password_digest: Mapped[str] = mapped_column(String(255), nullable=False)
admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
active: Mapped[bool] = mapped_column(
Boolean, default=True, nullable=False, server_default=text("1"),
)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
password_changed_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class Overlay(Base):
__tablename__ = "overlays"
__table_args__ = (
Index(
"uq_overlay_name_system",
"name",
unique=True,
sqlite_where=text("user_id IS NULL"),
),
Index(
"uq_overlay_name_per_user",
"name",
"user_id",
unique=True,
sqlite_where=text("user_id IS NOT NULL"),
),
Index("ix_overlays_type_user_id", "type", "user_id"),
{"sqlite_autoincrement": True},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
path: Mapped[str] = mapped_column(String(512), nullable=False)
type: Mapped[str] = mapped_column(String(16), nullable=False, default="workshop")
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
script: Mapped[str] = mapped_column(Text, default="", nullable=False)
last_build_status: Mapped[str] = mapped_column(String(16), default="", nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class WorkshopItem(Base):
__tablename__ = "workshop_items"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
steam_id: Mapped[str] = mapped_column(String(20), unique=True, nullable=False)
title: Mapped[str] = mapped_column(String(255), default="", nullable=False)
filename: Mapped[str] = mapped_column(String(255), default="", nullable=False)
file_url: Mapped[str] = mapped_column(Text, default="", nullable=False)
file_size: Mapped[int] = mapped_column(BigInteger, default=0, nullable=False)
time_updated: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
preview_url: Mapped[str] = mapped_column(Text, default="", nullable=False)
last_downloaded_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_error: Mapped[str] = mapped_column(Text, default="", nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class OverlayWorkshopItem(Base):
__tablename__ = "overlay_workshop_items"
__table_args__ = (
UniqueConstraint("overlay_id", "workshop_item_id", name="uq_overlay_workshop_item"),
Index("ix_owi_workshop_item", "workshop_item_id"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
overlay_id: Mapped[int] = mapped_column(
ForeignKey("overlays.id", ondelete="CASCADE"), nullable=False
)
workshop_item_id: Mapped[int] = mapped_column(
ForeignKey("workshop_items.id", ondelete="RESTRICT"), nullable=False
)
class Blueprint(Base):
__tablename__ = "blueprints"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
name: Mapped[str] = mapped_column(String(128), nullable=False)
arguments: Mapped[str] = mapped_column(Text, default="[]", nullable=False)
config: Mapped[str] = mapped_column(Text, default="[]", nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class BlueprintOverlay(Base):
__tablename__ = "blueprint_overlays"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
blueprint_id: Mapped[int] = mapped_column(ForeignKey("blueprints.id"), nullable=False)
overlay_id: Mapped[int] = mapped_column(ForeignKey("overlays.id"), nullable=False)
position: Mapped[int] = mapped_column(Integer, nullable=False)
expose_server_cfg: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False, server_default=text("0")
)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class Server(Base):
__tablename__ = "servers"
__table_args__ = (
UniqueConstraint("user_id", "name", name="uq_servers_user_name"),
)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
blueprint_id: Mapped[int] = mapped_column(ForeignKey("blueprints.id"), nullable=False)
name: Mapped[str] = mapped_column(String(128), nullable=False)
port: Mapped[int] = mapped_column(Integer, unique=True, nullable=False)
desired_state: Mapped[str] = mapped_column(String(16), default="stopped", nullable=False)
actual_state: Mapped[str] = mapped_column(String(16), default="unknown", nullable=False)
actual_state_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_error: Mapped[str] = mapped_column(Text, default="", nullable=False)
rcon_password: Mapped[str] = mapped_column(
String(64), nullable=False, default="", server_default=""
)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class Job(Base):
__tablename__ = "jobs"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
server_id: Mapped[int | None] = mapped_column(ForeignKey("servers.id"), nullable=True)
overlay_id: Mapped[int | None] = mapped_column(ForeignKey("overlays.id"), nullable=True)
operation: Mapped[str] = mapped_column(String(32), nullable=False)
state: Mapped[str] = mapped_column(String(16), default="queued", nullable=False)
exit_code: Mapped[int | None] = mapped_column(Integer, nullable=True)
started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
finished_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class JobLog(Base):
__tablename__ = "job_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
job_id: Mapped[int] = mapped_column(ForeignKey("jobs.id"), nullable=False)
seq: Mapped[int] = mapped_column(Integer, nullable=False)
stream: Mapped[str] = mapped_column(String(8), nullable=False)
line: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, nullable=False)
class ServerLiveState(Base):
__tablename__ = "server_live_state"
__table_args__ = (
Index("ix_sls_server_started", "server_id", "started_at"),
{"sqlite_autoincrement": True},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
server_id: Mapped[int] = mapped_column(
ForeignKey("servers.id", ondelete="CASCADE"), nullable=False
)
started_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
last_seen_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
players: Mapped[int] = mapped_column(Integer, nullable=False)
max_players: Mapped[int] = mapped_column(Integer, nullable=False)
bots: Mapped[int] = mapped_column(Integer, nullable=False)
map: Mapped[str] = mapped_column(String(64), nullable=False)
hibernating: Mapped[bool] = mapped_column(Boolean, nullable=False)
class ServerPlayerSession(Base):
__tablename__ = "server_player_session"
__table_args__ = (
Index("ix_sps_server_open", "server_id", "left_at"),
Index("ix_sps_server_recent", "server_id", "left_at"),
Index("ix_sps_steam_history", "steam_id_64", "joined_at"),
{"sqlite_autoincrement": True},
)
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
server_id: Mapped[int] = mapped_column(
ForeignKey("servers.id", ondelete="CASCADE"), nullable=False
)
steam_id_64: Mapped[str] = mapped_column(String(20), nullable=False)
joined_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
left_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
name_at_join: Mapped[str] = mapped_column(String(64), nullable=False)
min_ping: Mapped[int] = mapped_column(Integer, nullable=False)
max_ping: Mapped[int] = mapped_column(Integer, nullable=False)
class SteamUserProfile(Base):
__tablename__ = "steam_user_profile"
steam_id_64: Mapped[str] = mapped_column(String(20), primary_key=True)
persona_name: Mapped[str] = mapped_column(String(64), nullable=False)
avatar_url: Mapped[str] = mapped_column(Text, nullable=False)
fetched_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)