Data Model
server/app/models.py is the database source of truth. Alembic migrations in
server/alembic/versions/ apply model changes to PostgreSQL.
Main Entity Groups
| Group | Models |
|---|---|
| Accounts | User, EmailVerificationToken, PasswordResetToken |
| Campaign core | Campaign, Membership, Invite |
| Sessions and scheduling | Session, AvailabilityPoll, AvailabilityOption, AvailabilityResponse |
| Encounters and combat | Encounter, EncounterMonster, Combat, Combatant |
| NPCs | NPC, NpcSession, NpcRelationship |
| Codex | CampaignEntity, CampaignEntitySession, RecapExtraction |
| Documents and RAG | CoreRule, CampaignCoreRule, CampaignDocument, IngestionSetting, Chunk, Monster, Item, RulesChatMessage |
Design Choices
Enums are often stored as strings and validated in Pydantic or service logic. This keeps migrations lighter and allows feature iteration without repeated enum DDL churn.
Serializers shape API responses. Do not expose raw ORM objects to the frontend. Computed fields such as campaign counts, session labels, difficulty badges, NPC appearance counts, and AI access should be added in serializers or query helpers.
Campaign Scope
Most tables are scoped by campaignId. Always include scope checks in queries
for campaign-owned data. This is both a security rule and a correctness rule:
IDs alone are not enough to prove the current user can access a row.
Document Scope
Document chunks can belong to either:
- Core rules, through
coreRuleId. - Campaign documents, through
campaignDocumentIdandcampaignId.
Retrieval applies campaign scope in SQL. Required core books are always visible,
optional core books are visible only when enabled by CampaignCoreRule, and
tool-only books are never returned as chat sources.
Migration Workflow
When changing models:
cd server
uv run alembic revision --autogenerate -m "describe change"
uv run alembic upgrade head
uv run pytest
Review generated migrations before committing. Add indexes for foreign keys and
common filters. Update server/seed.py when sample data would otherwise stop
covering a new feature.