Character System: - Inventory system with 5,482 equipment items - Feats tab with categories and details - Actions tab with 99 PF2e actions - Item detail modal with equipment info - Feat detail modal with descriptions - Edit character modal with image cropping Auth & UI: - Animated login screen with splash → form transition - Letter-by-letter "DIMENSION 47" animation - Starfield background with floating orbs - Logo tap glow effect - "Remember me" functionality (localStorage/sessionStorage) Real-time Sync: - WebSocket gateway for character updates - Live sync for HP, conditions, inventory, equipment status, money, level Database: - Added credits field to characters - Added custom fields for items - Added feat fields and relations - Included full database backup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
608 lines
16 KiB
Plaintext
608 lines
16 KiB
Plaintext
// 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
|
|
}
|
|
|
|
// ==========================================
|
|
// 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[]
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
// ==========================================
|
|
// 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?
|
|
|
|
characterItems CharacterItem[]
|
|
}
|
|
|
|
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?
|
|
quality TranslationQuality @default(MEDIUM)
|
|
translatedBy String @default("claude-api")
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([type, englishName])
|
|
@@index([type])
|
|
@@index([englishName])
|
|
}
|