Files
parenthese/backend/prisma/schema.prisma
T
IliesAllaliandClaude Opus 5.5 5fc0c08c1c Journal des visites, recherche qui mène à la personne, croix de retour à l'arbre
- Visites : chaque ouverture de l'arbre est notée (prénom facultatif demandé avec le mot de passe, appareil, compte), sans adresse IP, effacée après un an ; onglet Visites dans les paramètres, réservé aux administrateurs
- Recherche : un résultat choisi recentre l'arbre sur la personne, la liste se replie
- Écran de compte ouvert depuis l'arbre : croix pour y revenir
- Enfant presque sous l'union : trait direct au lieu d'un petit S
- Page vie privée : paragraphe sur les visites

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 18:44:07 +02:00

382 lines
11 KiB
Plaintext

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum MembershipRole {
owner
admin
member
}
enum ContributionPolicy {
direct
pending
}
enum VisitorPolicy {
read_only
}
enum ContributionSessionStatus {
pending
approved
rejected
}
enum ContributionActorMode {
user
tree_access
}
enum ContributionEntityType {
person
union
parent_child_link
annotation
media
}
// Souvenir envoyé par la famille : en attente jusqu'à la relecture du propriétaire
enum MediaStatus {
approved
pending
rejected
}
enum AnnotationType {
drawing
sticker
text
photo
}
enum ContributionAction {
create
update
delete
reorder
}
enum ConflictState {
none
needs_review
}
enum ContributionDecision {
approved
rejected
}
enum SharedAccessRole {
visitor
contributor
}
model User {
id String @id @default(cuid())
email String @unique
passwordHash String
lastOpenedTreeId String?
lastOpenedAccessMode String?
lastOpenedRole String?
lastOpenedAt DateTime?
createdAt DateTime @default(now())
ownedTrees Tree[] @relation("TreeOwner")
memberships TreeMembership[]
sharedTreeAccesses UserTreeAccess[]
submittedContributionSessions ContributionSession[] @relation("ContributionSubmittedBy")
reviewedContributionSessions ContributionSession[] @relation("ContributionReviewedBy")
treeVisits TreeVisit[]
lastOpenedTree Tree? @relation("UserLastOpenedTree", fields: [lastOpenedTreeId], references: [id], onDelete: SetNull)
@@index([lastOpenedAt])
@@map("users")
}
model Tree {
id String @id @default(cuid())
ownerUserId String
slug String @unique
name String
description String?
rootPersonId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
owner User @relation("TreeOwner", fields: [ownerUserId], references: [id], onDelete: Restrict)
accessPasswords TreeAccessPasswords?
sharePassword TreeSharePassword?
memberships TreeMembership[]
sharedUserAccesses UserTreeAccess[]
settings TreeSettings?
persons Person[]
unions Union[]
parentChildLinks ParentChildLink[]
mediaItems MediaItem[]
contributionSessions ContributionSession[]
auditLogs AuditLog[]
annotations Annotation[]
visits TreeVisit[]
lastOpenedByUsers User[] @relation("UserLastOpenedTree")
@@index([ownerUserId])
@@map("trees")
}
model TreeAccessPasswords {
treeId String @id
visitorHash String
contributorHash String
updatedAt DateTime @updatedAt
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@map("tree_access_passwords")
}
// Mot de passe de partage en clair chiffré, pour que le propriétaire voie toujours celui en vigueur.
// Table à part : écrire dans tree_access_passwords changerait updatedAt, qui sert de version aux accès ouverts.
model TreeSharePassword {
treeId String @id
passwordEnc String
setAt DateTime @default(now())
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@map("tree_share_passwords")
}
model TreeMembership {
id String @id @default(cuid())
treeId String
userId String
role MembershipRole
contributionModeOverride ContributionPolicy?
createdAt DateTime @default(now())
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([treeId, userId])
@@index([userId])
@@map("tree_memberships")
}
model UserTreeAccess {
id String @id @default(cuid())
treeId String
userId String
role SharedAccessRole
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([treeId, userId])
@@index([userId, updatedAt])
@@map("user_tree_accesses")
}
model TreeSettings {
treeId String @id
memberContributionPolicy ContributionPolicy @default(pending)
visitorPolicy VisitorPolicy @default(read_only)
contributorPolicy ContributionPolicy @default(pending)
updatedAt DateTime @updatedAt
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@map("tree_settings")
}
model Person {
id String @id @default(cuid())
treeId String
firstName String
lastName String
birthName String?
birthDate DateTime?
deathDate DateTime?
birthPlace String?
profession String?
region String?
nationality String?
avatarPath String?
avatarMimeType String?
sex String?
notes String?
createdBy String?
updatedBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId])
@@map("persons")
}
model Union {
id String @id @default(cuid())
treeId String
partner1PersonId String
partner2PersonId String?
unionType String?
startDate DateTime?
endDate DateTime?
displayOrder Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId])
@@map("unions")
}
model ParentChildLink {
id String @id @default(cuid())
treeId String
parentPersonId String
childPersonId String
viaUnionId String?
parentageType String?
displayOrder Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId])
@@map("parent_child_links")
}
model MediaItem {
id String @id @default(cuid())
treeId String
personId String
type String
mimeType String
filePath String
thumbPath String?
caption String?
source String?
sizeBytes Int
displayOrder Int @default(1)
isFeatured Boolean @default(false)
uploadedBy String?
status MediaStatus @default(approved)
contributionSessionId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId, personId, displayOrder])
@@map("media_items")
}
model ContributionSession {
id String @id @default(cuid())
treeId String
submittedByUserId String?
submittedByLabel String
comment String?
mode ContributionActorMode
status ContributionSessionStatus @default(pending)
title String
createdAt DateTime @default(now())
reviewedAt DateTime?
reviewedBy String?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
submittedByUser User? @relation("ContributionSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
reviewer User? @relation("ContributionReviewedBy", fields: [reviewedBy], references: [id], onDelete: SetNull)
changes ContributionChange[]
@@index([treeId, status, createdAt])
@@index([submittedByUserId])
@@map("contribution_sessions")
}
model ContributionChange {
id String @id @default(cuid())
sessionId String
entityType ContributionEntityType
entityId String?
action ContributionAction
beforeJson Json?
afterJson Json?
conflictState ConflictState @default(none)
decision ContributionDecision?
createdAt DateTime @default(now())
session ContributionSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId])
@@map("contribution_changes")
}
model Annotation {
id String @id @default(cuid())
treeId String
type AnnotationType
x Float
y Float
content String
style Json @default("{}")
zIndex Int @default(0)
createdBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId])
@@map("annotations")
}
model AuditLog {
id String @id @default(cuid())
treeId String
actorType String
actorId String?
action String
entityType String
entityId String
payloadJson Json?
createdAt DateTime @default(now())
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
@@index([treeId, createdAt])
@@map("audit_log")
}
// Journal des visites d'un arbre, lu par ses administrateurs (onglet Visites).
// Pas d'adresse IP : visitorKey est une empreinte (arbre + IP + navigateur) qui sert à compter les visiteurs distincts.
model TreeVisit {
id String @id @default(cuid())
treeId String
userId String?
accessKind String
visitorName String?
visitorKey String
device String
createdAt DateTime @default(now())
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
@@index([treeId, createdAt])
@@index([treeId, visitorKey, createdAt])
@@map("tree_visits")
}
model WaitlistEntry {
id String @id @default(cuid())
email String @unique
prenom String?
source String @default("direct")
createdAt DateTime @default(now())
@@map("waitlist_entries")
}