mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-15 08:32:03 +00:00
Nouvelle table life_events (baptême, résidence, métiers, études, service militaire, inhumation, divorce, événements libres…) avec date, précision, lieu, description, notes et sources. Personne : précision et borne des dates de naissance et de décès, lieu de décès, surnom. Union : lieu et précision du mariage. Naissance, décès et mariage gardent leur date et leur lieu sur la personne ou l'union ; leur ligne d'événement ne porte que les sources et les notes. Migration additive. L'import lit tous ces événements (balises de la norme, variantes Gramps, Heredis, RootsMagic), les adresses complètes, et les adoptions déclarées par PEDI. Une personne ou une union déjà présente ne reçoit rien en double. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
438 lines
13 KiB
Plaintext
438 lines
13 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?
|
|
immichConfig TreeImmichConfig?
|
|
persons Person[]
|
|
unions Union[]
|
|
parentChildLinks ParentChildLink[]
|
|
mediaItems MediaItem[]
|
|
contributionSessions ContributionSession[]
|
|
auditLogs AuditLog[]
|
|
annotations Annotation[]
|
|
visits TreeVisit[]
|
|
lifeEvents LifeEvent[]
|
|
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")
|
|
}
|
|
|
|
// Connexion Immich propre à chaque arbre (reconnaissance des visages) : l'adresse et la clé de
|
|
// l'instance sont choisies par ses administrateurs, jamais héritées du serveur. La clé est
|
|
// chiffrée au repos ; une ligne absente signifie « aucun accès Immich » pour cet arbre.
|
|
model TreeImmichConfig {
|
|
treeId String @id
|
|
apiUrl String
|
|
apiKeyEnc String
|
|
updatedAt DateTime @updatedAt
|
|
tree Tree @relation(fields: [treeId], references: [id], onDelete: Cascade)
|
|
|
|
@@map("tree_immich_configs")
|
|
}
|
|
|
|
model Person {
|
|
id String @id @default(cuid())
|
|
treeId String
|
|
firstName String
|
|
lastName String
|
|
birthName String?
|
|
birthDate DateTime?
|
|
deathDate DateTime?
|
|
birthPlace String?
|
|
// Précision d'une date importée (« vers », « avant », « entre »…) et sa borne de fin pour « entre … et … »
|
|
birthDatePrecision String?
|
|
birthDateEnd DateTime?
|
|
deathDatePrecision String?
|
|
deathDateEnd DateTime?
|
|
deathPlace String?
|
|
nickname String?
|
|
profession String?
|
|
region String?
|
|
nationality String?
|
|
avatarPath String?
|
|
avatarMimeType String?
|
|
sex String?
|
|
notes String?
|
|
createdBy String?
|
|
updatedBy String?
|
|
// Personne Immich associée (reconnaissance des visages) : identifiant côté Immich, ou null.
|
|
immichPersonId 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)
|
|
startPlace String?
|
|
startDatePrecision String?
|
|
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")
|
|
}
|
|
|
|
// Événements de vie d'une personne (baptême, résidence, métier, études, service militaire, inhumation…) ou
|
|
// d'un couple (divorce…). Naissance, décès et mariage gardent leur date et leur lieu sur la personne ou
|
|
// l'union ; leur ligne ici ne porte que les sources et les notes (type birth, death, marriage).
|
|
model LifeEvent {
|
|
id String @id @default(cuid())
|
|
treeId String
|
|
personId String?
|
|
unionId String?
|
|
type String
|
|
label String?
|
|
date DateTime?
|
|
dateEnd DateTime?
|
|
datePrecision String?
|
|
dateText String?
|
|
place String?
|
|
description String?
|
|
notes String?
|
|
sources 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])
|
|
@@index([treeId, personId])
|
|
@@index([treeId, unionId])
|
|
@@map("life_events")
|
|
}
|