// Dimension47 - TTRPG Campaign Management Platform // Prisma Schema generator client { provider = "prisma-client" output = "../src/generated/prisma" moduleFormat = "cjs" } datasource db { provider = "postgresql" } // ========================================== // ENUMS // ========================================== enum UserRole { ADMIN GM PLAYER } enum CharacterType { PC NPC } enum AbilityType { STR DEX CON INT WIS CHA } enum Proficiency { UNTRAINED TRAINED EXPERT MASTER LEGENDARY } enum FeatSource { CLASS ANCESTRY GENERAL SKILL BONUS ARCHETYPE } enum SpellTradition { ARCANE DIVINE OCCULT PRIMAL } enum CombatantType { PC NPC MONSTER } enum ActionType { ACTION REACTION FREE } enum HighlightColor { YELLOW GREEN BLUE PINK } enum TranslationType { FEAT EQUIPMENT SPELL TRAIT ANCESTRY HERITAGE CLASS BACKGROUND CONDITION ACTION } enum TranslationQuality { HIGH MEDIUM LOW } enum ResearchField { BOMBER CHIRURGEON MUTAGENIST TOXICOLOGIST } // ========================================== // USER & AUTH // ========================================== model User { id String @id @default(uuid()) username String @unique email String @unique passwordHash String role UserRole @default(PLAYER) avatarUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations gmCampaigns Campaign[] @relation("CampaignGM") campaignMembers CampaignMember[] characters Character[] @relation("CharacterOwner") uploadedDocuments Document[] documentAccess DocumentAccess[] highlights Highlight[] notes Note[] noteShares NoteShare[] } // ========================================== // CAMPAIGNS // ========================================== model Campaign { id String @id @default(uuid()) name String description String? gmId String imageUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations gm User @relation("CampaignGM", fields: [gmId], references: [id]) members CampaignMember[] characters Character[] battleMaps BattleMap[] combatants Combatant[] battleSessions BattleSession[] documents Document[] notes Note[] } model CampaignMember { campaignId String userId String joinedAt DateTime @default(now()) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@id([campaignId, userId]) } // ========================================== // CHARACTERS (Normalisiert) // ========================================== model Character { id String @id @default(uuid()) campaignId String ownerId String? name String type CharacterType @default(PC) level Int @default(1) avatarUrl String? // Core Stats hpCurrent Int hpMax Int hpTemp Int @default(0) // Ancestry/Class/Background (References) ancestryId String? heritageId String? classId String? backgroundId String? // Experience experiencePoints Int @default(0) // Currency (Ironvale uses Credits instead of Gold/Silver/Copper) credits Int @default(0) // Pathbuilder Import Data (JSON blob for original import) pathbuilderData Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // Relations campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) owner User? @relation("CharacterOwner", fields: [ownerId], references: [id]) abilities CharacterAbility[] feats CharacterFeat[] skills CharacterSkill[] spells CharacterSpell[] items CharacterItem[] conditions CharacterCondition[] resources CharacterResource[] battleTokens BattleToken[] documentAccess DocumentAccess[] // Alchemy alchemyState CharacterAlchemyState? formulas CharacterFormula[] preparedItems CharacterPreparedItem[] } model CharacterAbility { id String @id @default(uuid()) characterId String ability AbilityType score Int character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) @@unique([characterId, ability]) } model CharacterFeat { id String @id @default(uuid()) characterId String featId String? // Reference to Feat table name String // English name nameGerman String? // German translation level Int // Level obtained source FeatSource character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) feat Feat? @relation(fields: [featId], references: [id]) } model CharacterSkill { id String @id @default(uuid()) characterId String skillName String proficiency Proficiency @default(UNTRAINED) character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) @@unique([characterId, skillName]) } model CharacterSpell { id String @id @default(uuid()) characterId String spellId String? name String nameGerman String? tradition SpellTradition spellLevel Int prepared Boolean @default(false) character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) spell Spell? @relation(fields: [spellId], references: [id]) } model CharacterItem { id String @id @default(uuid()) characterId String equipmentId String? // Reference to Equipment library name String nameGerman String? quantity Int @default(1) bulk Decimal @default(0) equipped Boolean @default(false) invested Boolean @default(false) containerId String? // For containers notes String? // Player-editable: Alias/Nickname for the item alias String? // GM-editable: Custom overrides for item properties customName String? // Override display name customDamage String? // Override damage dice (e.g. "2d6") customDamageType String? // Override damage type (e.g. "fire") customTraits String[] // Override/add traits customRange String? // Override range customHands String? // Override hands requirement character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) equipment Equipment? @relation(fields: [equipmentId], references: [id]) } model CharacterCondition { id String @id @default(uuid()) characterId String name String nameGerman String? value Int? // For valued conditions like Frightened 2 duration String? source String? character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) } model CharacterResource { id String @id @default(uuid()) characterId String name String current Int max Int character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) @@unique([characterId, name]) } // ========================================== // ALCHEMY SYSTEM // ========================================== model CharacterAlchemyState { id String @id @default(uuid()) characterId String @unique researchField ResearchField? versatileVialsCurrent Int @default(0) versatileVialsMax Int @default(0) advancedAlchemyBatch Int @default(0) advancedAlchemyMax Int @default(0) lastRestAt DateTime? character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) } model CharacterFormula { id String @id @default(uuid()) characterId String equipmentId String name String nameGerman String? learnedAt Int @default(1) formulaSource String? // "Pathbuilder Import", "Purchased", "Found" character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) equipment Equipment @relation(fields: [equipmentId], references: [id]) @@unique([characterId, equipmentId]) @@index([characterId]) } model CharacterPreparedItem { id String @id @default(uuid()) characterId String equipmentId String name String nameGerman String? quantity Int @default(1) isQuickAlchemy Boolean @default(false) isInfused Boolean @default(true) // Infused items expire on rest, permanent items don't createdAt DateTime @default(now()) character Character @relation(fields: [characterId], references: [id], onDelete: Cascade) equipment Equipment @relation(fields: [equipmentId], references: [id]) @@index([characterId]) } // ========================================== // BATTLE SYSTEM // ========================================== model BattleMap { id String @id @default(uuid()) campaignId String name String imageUrl String gridSizeX Int @default(20) gridSizeY Int @default(20) gridOffsetX Int @default(0) gridOffsetY Int @default(0) createdAt DateTime @default(now()) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) battleSessions BattleSession[] } model Combatant { id String @id @default(uuid()) campaignId String name String type CombatantType level Int hpMax Int ac Int // Saves fortitude Int reflex Int will Int perception Int // Speed speed Int @default(25) avatarUrl String? description String? createdAt DateTime @default(now()) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) abilities CombatantAbility[] battleTokens BattleToken[] } model CombatantAbility { id String @id @default(uuid()) combatantId String name String actionCost Int // 1, 2, 3, or 0 for free/reaction actionType ActionType description String damage String? // e.g. "2d6+4 slashing" traits String[] combatant Combatant @relation(fields: [combatantId], references: [id], onDelete: Cascade) } model BattleSession { id String @id @default(uuid()) campaignId String mapId String? name String? isActive Boolean @default(false) roundNumber Int @default(0) createdAt DateTime @default(now()) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) map BattleMap? @relation(fields: [mapId], references: [id]) tokens BattleToken[] } model BattleToken { id String @id @default(uuid()) battleSessionId String combatantId String? characterId String? name String positionX Float positionY Float hpCurrent Int hpMax Int initiative Int? conditions String[] size Int @default(1) // 1 = Medium, 2 = Large, etc. battleSession BattleSession @relation(fields: [battleSessionId], references: [id], onDelete: Cascade) combatant Combatant? @relation(fields: [combatantId], references: [id]) character Character? @relation(fields: [characterId], references: [id]) } // ========================================== // DOCUMENTS SYSTEM // ========================================== model Document { id String @id @default(uuid()) campaignId String title String description String? category String? tags String[] filePath String fileType String // html, pdf uploadedBy String createdAt DateTime @default(now()) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) uploader User @relation(fields: [uploadedBy], references: [id]) access DocumentAccess[] highlights Highlight[] } model DocumentAccess { id String @id @default(uuid()) documentId String userId String? // NULL = all campaign members characterId String? // Access per character document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id]) character Character? @relation(fields: [characterId], references: [id]) @@unique([documentId, userId, characterId]) } model Highlight { id String @id @default(uuid()) documentId String userId String selectionText String startOffset Int endOffset Int color HighlightColor note String? createdAt DateTime @default(now()) document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id]) } model Note { id String @id @default(uuid()) userId String campaignId String title String content String // Markdown isShared Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) campaign Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) shares NoteShare[] } model NoteShare { noteId String userId String note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id]) @@id([noteId, userId]) } // ========================================== // REFERENCE DATA (Pathfinder 2e) // ========================================== model Feat { id String @id @default(uuid()) name String @unique traits String[] summary String? description String? // Full feat description/benefit text actions String? // "1", "2", "3", "free", "reaction", null for passive url String? level Int? // Minimum level requirement sourceBook String? // Feat classification featType String? // "General", "Skill", "Class", "Ancestry", "Archetype", "Heritage" rarity String? // "Common", "Uncommon", "Rare", "Unique" // Prerequisites prerequisites String? // Text description of prerequisites // For class/archetype feats className String? // "Fighter", "Wizard", etc. archetypeName String? // "Sentinel", "Medic", etc. // For ancestry feats ancestryName String? // "Human", "Elf", etc. // For skill feats skillName String? // "Acrobatics", "Athletics", etc. // Cached German translation nameGerman String? summaryGerman String? characterFeats CharacterFeat[] @@index([featType]) @@index([className]) @@index([ancestryName]) @@index([level]) } model Equipment { id String @id @default(uuid()) name String @unique traits String[] itemCategory String // "Weapons", "Armor", "Consumables", "Shields", etc. itemSubcategory String? bulk String? // "L" for light, "1", "2", etc. url String? summary String? level Int? price Int? // In CP // Weapon-specific fields hands String? damage String? // "1d8 S", "1d6 P", etc. damageType String? // "S", "P", "B" (Slashing, Piercing, Bludgeoning) range String? reload String? weaponCategory String? // "Simple", "Martial", "Advanced", "Ammunition" weaponGroup String? // "Sword", "Axe", "Bow", etc. // Armor-specific fields ac Int? dexCap Int? checkPenalty Int? speedPenalty Int? strength Int? // Strength requirement armorCategory String? // "Unarmored", "Light", "Medium", "Heavy" armorGroup String? // "Leather", "Chain", "Plate", etc. // Shield-specific fields shieldHp Int? shieldHardness Int? shieldBt Int? // Broken Threshold // Consumable/Equipment-specific fields activation String? // "Cast A Spell", "[one-action]", etc. duration String? usage String? effect String? // Specific effect text for item variants (Lesser, Moderate, Greater, Major) characterItems CharacterItem[] formulas CharacterFormula[] preparedItems CharacterPreparedItem[] } model Spell { id String @id @default(uuid()) name String @unique level Int actions String? traditions String[] traits String[] range String? targets String? duration String? description String? url String? characterSpells CharacterSpell[] } model Trait { id String @id @default(uuid()) name String @unique description String? url String? } // ========================================== // TRANSLATION CACHE // ========================================== model Translation { id String @id @default(uuid()) type TranslationType englishName String germanName String germanSummary String? germanDescription String? germanEffect String? // Translated effect text for alchemical items quality TranslationQuality @default(MEDIUM) translatedBy String @default("claude-api") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([type, englishName]) @@index([type]) @@index([englishName]) }