"""ORM models for the two content-bank games (PR4): * ``arcade_phishing_samples`` — rendered emails for 钓鱼邮件鉴别; each is phishing or legit, with the tell-tale flags + a teaching explanation. * ``arcade_spot_scenes`` — office scenes for 安全找茬; each carries a list of scene ELEMENTS, a subset of which are security hazards (the player selects the hazards). Element truth + reasons stay server-side. Both created by migration 414. Editable by admins + AI-generatable, same pattern as the trivia bank. """ from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import ( JSON, Boolean, DateTime, ForeignKey, Index, String, Text, func, ) from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base from app.core.sql_types import GUID as Uuid STATUS_ACTIVE = "active" STATUS_ARCHIVED = "archived" SOURCE_SEED = "seed" SOURCE_AI = "ai" SOURCE_MANUAL = "manual" class ArcadePhishingSample(Base): __tablename__ = "arcade_phishing_samples" __table_args__ = ( Index("ix_arcade_phish_status", "status"), ) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) sender_name: Mapped[str] = mapped_column(String(128), nullable=False) sender_email: Mapped[str] = mapped_column(String(256), nullable=False) subject: Mapped[str] = mapped_column(String(256), nullable=False) body: Mapped[str] = mapped_column(Text, nullable=False) is_phishing: Mapped[bool] = mapped_column(Boolean, nullable=False) # JSON list of short flag strings (what gives it away / why it's safe) — # shown in the result reveal. Player-facing content. flags: Mapped[list] = mapped_column(JSON, nullable=False, default=list) explanation: Mapped[str | None] = mapped_column(Text) status: Mapped[str] = mapped_column(String(16), nullable=False, default=STATUS_ACTIVE) source: Mapped[str] = mapped_column(String(16), nullable=False, default=SOURCE_SEED) created_by: Mapped[uuid.UUID | None] = mapped_column(Uuid, ForeignKey("identity_persons.id")) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) class ArcadeSpotScene(Base): __tablename__ = "arcade_spot_scenes" __table_args__ = ( Index("ix_arcade_spot_status", "status"), ) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) title: Mapped[str] = mapped_column(String(128), nullable=False) # JSON list of {"key","label","hazard":bool,"reason":str}. The player is # shown label + an icon; hazard/reason are the server-side truth. elements: Mapped[list] = mapped_column(JSON, nullable=False) status: Mapped[str] = mapped_column(String(16), nullable=False, default=STATUS_ACTIVE) source: Mapped[str] = mapped_column(String(16), nullable=False, default=SOURCE_SEED) created_by: Mapped[uuid.UUID | None] = mapped_column(Uuid, ForeignKey("identity_persons.id")) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) __all__ = [ "ArcadePhishingSample", "ArcadeSpotScene", "STATUS_ACTIVE", "STATUS_ARCHIVED", "SOURCE_SEED", "SOURCE_AI", "SOURCE_MANUAL", ]