Initial commit: Dimension47 project setup
- NestJS backend with JWT auth, Prisma ORM, Swagger docs - Vite + React 19 frontend with TypeScript - Tailwind CSS v4 with custom dark theme design system - Auth module: Login, Register, Protected routes - Campaigns module: CRUD, Member management - Full Prisma schema for PF2e campaign management - Docker Compose for PostgreSQL Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
542
server/prisma/schema.prisma
Normal file
542
server/prisma/schema.prisma
Normal file
@@ -0,0 +1,542 @@
|
||||
// Dimension47 - TTRPG Campaign Management Platform
|
||||
// Prisma Schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
output = "../src/generated/prisma"
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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?
|
||||
|
||||
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?
|
||||
actions String?
|
||||
url String?
|
||||
level Int?
|
||||
sourceBook String?
|
||||
|
||||
characterFeats CharacterFeat[]
|
||||
}
|
||||
|
||||
model Equipment {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
traits String[]
|
||||
itemCategory String
|
||||
itemSubcategory String?
|
||||
bulk String?
|
||||
url String?
|
||||
summary String?
|
||||
activation String?
|
||||
hands String?
|
||||
damage String?
|
||||
range String?
|
||||
weaponCategory String?
|
||||
price Int? // In CP
|
||||
level Int?
|
||||
|
||||
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])
|
||||
}
|
||||
Reference in New Issue
Block a user