@danielcok17/prisma-db 1.13.6 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielcok17/prisma-db",
3
- "version": "1.13.6",
3
+ "version": "1.14.1",
4
4
  "description": "Shared Prisma schema for Legal AI applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "scripts": {
16
16
  "build": "npm run db:generate && tsc",
17
17
  "dev": "tsc --watch",
18
- "db:generate": "npx prisma generate --schema ./prisma/app.prisma && npx prisma generate --schema ./prisma/law.prisma",
18
+ "db:generate": "prisma generate --schema ./prisma/app.prisma && prisma generate --schema ./prisma/law.prisma",
19
19
  "db:push": "prisma db push --schema ./prisma/app.prisma",
20
20
  "db:migrate": "prisma migrate dev --schema ./prisma/app.prisma",
21
21
  "db:migrate:prod": "prisma migrate deploy --schema ./prisma/app.prisma",
package/prisma/app.prisma CHANGED
@@ -23,95 +23,102 @@ model Account {
23
23
  scope String?
24
24
  id_token String?
25
25
  session_state String?
26
+ brand String @default("smartlex") // Multi-brand: "smartlex" | "justi"
26
27
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
27
28
 
28
- @@unique([provider, providerAccountId])
29
+ @@unique([provider, providerAccountId, brand])
29
30
  }
30
31
 
31
32
  model User {
32
- id String @id @default(cuid())
33
- name String?
34
- email String? @unique
35
- emailVerified DateTime?
36
- image String?
33
+ id String @id @default(cuid())
34
+ brand String @default("smartlex") // Multi-brand: "smartlex" | "justi"
35
+ name String?
36
+ email String?
37
+ emailVerified DateTime?
38
+ image String?
37
39
  // Credentials authentication fields
38
- password String? // Hashed password pre credentials login
39
- createdAt DateTime @default(now())
40
- agreedToTerms Boolean @default(false)
41
- practiceArea String[]
42
- lawFirm String?
43
- yearsOfExperience Int?
40
+ password String? // Hashed password pre credentials login
41
+ createdAt DateTime @default(now())
42
+ agreedToTerms Boolean @default(false)
43
+ practiceArea String[]
44
+ lawFirm String?
45
+ yearsOfExperience Int?
44
46
  // Nové polia pre schvalovanie používateľov
45
- isApproved Boolean @default(false) // Či je používateľ schválený
46
- isRejected Boolean @default(false) // Či je používateľ zamietnutý
47
- approvedAt DateTime? // Kedy bol schválený
48
- rejectedAt DateTime? // Kedy bol zamietnutý
49
- approvedBy String? // ID admina, ktorý schválil
50
- rejectedBy String? // ID admina, ktorý zamietol
51
- rejectionReason String? // Dôvod zamietnutia
47
+ isApproved Boolean @default(false) // Či je používateľ schválený
48
+ isRejected Boolean @default(false) // Či je používateľ zamietnutý
49
+ approvedAt DateTime? // Kedy bol schválený
50
+ rejectedAt DateTime? // Kedy bol zamietnutý
51
+ approvedBy String? // ID admina, ktorý schválil
52
+ rejectedBy String? // ID admina, ktorý zamietol
53
+ rejectionReason String? // Dôvod zamietnutia
52
54
  // Nové polia pre tracking a žiadosť
53
- referralSource String? // Odkiaľ sa o nás dozvedel (Google, Facebook, LinkedIn, etc.)
54
- applicationText String? // Text žiadosti o prihlásenie
55
+ referralSource String? // Odkiaľ sa o nás dozvedel (Google, Facebook, LinkedIn, etc.)
56
+ applicationText String? // Text žiadosti o prihlásenie
55
57
  // ✨ STRIPE FIELDS (B2C - len pre individuálnych používateľov)
56
- subscriptionTier SubscriptionTier @default(FREE) // Aktuálny subscription tier (cache)
57
- messageCount Int @default(4) // Free tier default
58
- messageCountResetAt DateTime? // Kedy sa má resetovať message count
59
- adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
58
+ subscriptionTier SubscriptionTier @default(FREE) // Aktuálny subscription tier (cache)
59
+ messageCount Int @default(10) // OPRAVENÉ z 100 na 10
60
+ messageCountResetAt DateTime? // Kedy sa má resetovať message count
61
+ adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
60
62
  // ✨ COMPANY FIELDS (pre SZČO - samostatne zárobkovo činná osoba)
61
- customerType CustomerType @default(INDIVIDUAL) // Typ zákazníka: fyzická osoba alebo SZČO
62
- companyName String? // Obchodné meno (pre SZČO)
63
- companyNumber String? // IČO (povinné pre SZČO, 8 číslic)
64
- vatNumber String? // IČ DPH (SK + 10 číslic, nepovinné)
65
- taxNumber String? // DIČ (10 číslic, nepovinné)
66
- companyStreet String? // Ulica a číslo (pre SZČO)
67
- companyCity String? // Mesto (pre SZČO)
68
- companyPostalCode String? // PSČ (pre SZČO, formát: XXX XX)
69
- companyCountry String? @default("SK") // Krajina (ISO kód, default SK)
63
+ customerType CustomerType @default(INDIVIDUAL) // Typ zákazníka: fyzická osoba alebo SZČO
64
+ companyName String? // Obchodné meno (pre SZČO)
65
+ companyNumber String? // IČO (povinné pre SZČO, 8 číslic)
66
+ vatNumber String? // IČ DPH (SK + 10 číslic, nepovinné)
67
+ taxNumber String? // DIČ (10 číslic, nepovinné)
68
+ companyStreet String? // Ulica a číslo (pre SZČO)
69
+ companyCity String? // Mesto (pre SZČO)
70
+ companyPostalCode String? // PSČ (pre SZČO, formát: XXX XX)
71
+ companyCountry String? @default("SK") // Krajina (ISO kód, default SK)
70
72
  // ✨ UTM TRACKING & ATTRIBUTION FIELDS
71
73
  // First-visit attribution (celý lifecycle)
72
- firstUtmSource String? // Prvý UTM source
73
- firstUtmMedium String? // Prvý UTM medium
74
- firstUtmCampaign String? // Prvá kampaň
75
- firstUtmContent String? // Prvý content/variant
76
- firstUtmTerm String? // Prvé keywords
77
- firstVisitAt DateTime? // Kedy prvýkrát navštívil web
74
+ firstUtmSource String? // Prvý UTM source
75
+ firstUtmMedium String? // Prvý UTM medium
76
+ firstUtmCampaign String? // Prvá kampaň
77
+ firstUtmContent String? // Prvý content/variant
78
+ firstUtmTerm String? // Prvé keywords
79
+ firstVisitAt DateTime? // Kedy prvýkrát navštívil web
78
80
  // Registration attribution
79
- registrationUtmSource String? // UTM source pri registrácii
80
- registrationUtmMedium String? // UTM medium pri registrácii
81
- registrationUtmCampaign String? // Kampaň pri registrácii
82
- registrationUtmContent String? // Content/variant pri registrácii
83
- registrationUtmTerm String? // Keywords pri registrácii
84
- // Profile fields
85
- phone String? // Mobilný telefón
86
- occupation String? // Povolanie (Právnik, Účtovník, etc.)
87
- preferredLanguage String? @default("sk") // Jazyk odpovedi (sk, cs, en)
81
+ registrationUtmSource String? // UTM source pri registrácii
82
+ registrationUtmMedium String? // UTM medium pri registrácii
83
+ registrationUtmCampaign String? // Kampaň pri registrácii
84
+ registrationUtmContent String? // Content/variant pri registrácii
85
+ registrationUtmTerm String? // Keywords pri registrácii
86
+ // Onboarding fields
87
+ occupation String?
88
+ phone String?
89
+ phoneVerified DateTime?
90
+ preferredLanguage String? @default("sk")
88
91
  // Relations
89
- approvalRequest UserApprovalRequest?
90
- stripeCustomer StripeCustomer? // ✨ B2C Stripe customer
91
- ownedOrganizations Organization[] @relation("OrganizationOwner")
92
- organizationMembers OrganizationMember[]
93
- createdInvites OrganizationInvite[] @relation("InviteCreator")
94
- accounts Account[]
95
- answers Answer[]
96
- conversations Conversation[]
97
- feedbacks Feedback[]
98
- pageViews PageView[]
99
- sessions Session[]
100
- workflowLogs WorkflowLog[]
101
- verificationTokens VerificationToken[]
102
- passwordResetTokens PasswordResetToken[]
103
- canvasDocuments CanvasDocument[]
104
- canvasDocumentVersions CanvasDocumentVersion[]
92
+ approvalRequest UserApprovalRequest?
93
+ stripeCustomer StripeCustomer? // ✨ B2C Stripe customer
94
+ ownedOrganizations Organization[] @relation("OrganizationOwner")
95
+ organizationMembers OrganizationMember[]
96
+ createdInvites OrganizationInvite[] @relation("InviteCreator")
97
+ accounts Account[]
98
+ answers Answer[]
99
+ conversations Conversation[]
100
+ feedbacks Feedback[]
101
+ pageViews PageView[]
102
+ sessions Session[]
103
+ workflowLogs WorkflowLog[]
104
+ verificationTokens VerificationToken[]
105
+ passwordResetTokens PasswordResetToken[]
106
+ phoneOtps PhoneOtp[]
107
+ canvasDocuments CanvasDocument[]
108
+ canvasDocumentVersions CanvasDocumentVersion[]
105
109
  // Folder system relations
106
- ownedFolders Folder[] @relation("FolderOwner")
107
- addedFolderItems FolderItem[] @relation("FolderItemAdder")
108
- folderActivities FolderActivity[] @relation("FolderActivityUser")
109
- folderSharesReceived FolderShare[] @relation("FolderShareUser")
110
- createdFolderShares FolderShare[] @relation("FolderShareCreator")
110
+ ownedFolders Folder[] @relation("FolderOwner")
111
+ addedFolderItems FolderItem[] @relation("FolderItemAdder")
112
+ folderActivities FolderActivity[] @relation("FolderActivityUser")
113
+ folderSharesReceived FolderShare[] @relation("FolderShareUser")
114
+ createdFolderShares FolderShare[] @relation("FolderShareCreator")
111
115
  // UTM tracking relations
112
- touchPoints TouchPoint[]
113
- conversions Conversion[]
116
+ touchPoints TouchPoint[]
117
+ conversions Conversion[]
114
118
 
119
+ // Multi-brand: compound unique allows same email across brands
120
+ @@unique([email, brand])
121
+ @@index([brand])
115
122
  @@index([customerType])
116
123
  @@index([companyNumber])
117
124
  @@index([email, customerType])
@@ -145,43 +152,43 @@ model UserApprovalRequest {
145
152
 
146
153
  // Organization = Právna kancelária / Firma
147
154
  model Organization {
148
- id String @id @default(cuid())
149
- name String // "Advokátska kancelária XYZ s.r.o."
155
+ id String @id @default(cuid())
156
+ name String // "Advokátska kancelária XYZ s.r.o."
150
157
 
151
158
  // Billing & Legal Information
152
- companyNumber String? // IČO (Identifikačné číslo organizácie)
153
- vatNumber String? // IČ DPH (VAT Number)
154
- taxNumber String? // DIČ (Daňové identifikačné číslo)
155
- legalForm String? // "s.r.o.", "a.s.", "v.o.s.", "k.s.", "SZČO"
156
- billingEmail String
159
+ companyNumber String? // IČO (Identifikačné číslo organizácie)
160
+ vatNumber String? // IČ DPH (VAT Number)
161
+ taxNumber String? // DIČ (Daňové identifikačné číslo)
162
+ legalForm String? // "s.r.o.", "a.s.", "v.o.s.", "k.s.", "SZČO"
163
+ billingEmail String
157
164
 
158
165
  // Address
159
- street String?
160
- city String?
161
- postalCode String?
162
- country String @default("SK")
166
+ street String?
167
+ city String?
168
+ postalCode String?
169
+ country String @default("SK")
163
170
 
164
171
  // Subscription (B2B)
165
172
  subscriptionTier SubscriptionTier @default(FREE) // Tier organizácie (cache)
166
- messageCount Int @default(0) // Spoločný pool správ pre všetkých členov
167
- messageLimit Int @default(4) // Free tier default
168
- messageCountResetAt DateTime? // Kedy sa má resetovať pool
169
- adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
173
+ messageCount Int @default(0) // Spoločný pool správ pre všetkých členov
174
+ messageLimit Int @default(100) // Limit správ podľa tieru
175
+ messageCountResetAt DateTime? // Kedy sa má resetovať pool
176
+ adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
170
177
 
171
178
  // Settings
172
- isActive Boolean @default(true)
173
- maxMembers Int @default(5) // Max počet členov podľa tieru
179
+ isActive Boolean @default(true)
180
+ maxMembers Int @default(5) // Max počet členov podľa tieru
174
181
 
175
182
  // Relations
176
- owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
183
+ owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
177
184
  ownerId String
178
185
  members OrganizationMember[]
179
186
  invites OrganizationInvite[]
180
187
  stripeCustomer StripeCustomer?
181
188
  folderShares FolderShare[]
182
189
 
183
- createdAt DateTime @default(now())
184
- updatedAt DateTime @updatedAt
190
+ createdAt DateTime @default(now())
191
+ updatedAt DateTime @updatedAt
185
192
 
186
193
  @@index([ownerId])
187
194
  @@index([vatNumber])
@@ -192,14 +199,14 @@ model Organization {
192
199
 
193
200
  // Many-to-Many: User ↔ Organization
194
201
  model OrganizationMember {
195
- id String @id @default(cuid())
202
+ id String @id @default(cuid())
196
203
  organizationId String
197
204
  userId String
198
- role MemberRole @default(MEMBER)
199
- joinedAt DateTime @default(now())
205
+ role MemberRole @default(MEMBER)
206
+ joinedAt DateTime @default(now())
200
207
 
201
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
202
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
208
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
209
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
203
210
 
204
211
  @@unique([organizationId, userId])
205
212
  @@index([organizationId])
@@ -209,23 +216,23 @@ model OrganizationMember {
209
216
 
210
217
  // Organization Invite System
211
218
  model OrganizationInvite {
212
- id String @id @default(cuid())
219
+ id String @id @default(cuid())
213
220
  organizationId String
214
- token String @unique
215
- email String? // Optional: specific email invite
216
- role MemberRole @default(MEMBER)
221
+ token String @unique
222
+ email String? // Optional: specific email invite
223
+ role MemberRole @default(MEMBER)
217
224
  createdBy String
218
225
  expiresAt DateTime
219
- maxUses Int @default(1) // For multi-use invites
220
- currentUses Int @default(0)
221
- isActive Boolean @default(true)
222
- usedAt DateTime? // Last usage timestamp
223
- usedBy String? // Last user who used it
224
- createdAt DateTime @default(now())
225
- updatedAt DateTime @updatedAt
226
+ maxUses Int @default(1) // For multi-use invites
227
+ currentUses Int @default(0)
228
+ isActive Boolean @default(true)
229
+ usedAt DateTime? // Last usage timestamp
230
+ usedBy String? // Last user who used it
231
+ createdAt DateTime @default(now())
232
+ updatedAt DateTime @updatedAt
226
233
 
227
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
228
- creator User @relation("InviteCreator", fields: [createdBy], references: [id])
234
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
235
+ creator User @relation("InviteCreator", fields: [createdBy], references: [id])
229
236
 
230
237
  @@index([token])
231
238
  @@index([organizationId])
@@ -234,26 +241,27 @@ model OrganizationInvite {
234
241
  }
235
242
 
236
243
  enum MemberRole {
237
- OWNER // Zakladateľ/vlastník, plný prístup k billing a settings
238
- ADMIN // Administrátor, môže pridávať/odoberať členov
239
- MEMBER // Bežný člen, len používa systém
244
+ OWNER // Zakladateľ/vlastník, plný prístup k billing a settings
245
+ ADMIN // Administrátor, môže pridávať/odoberať členov
246
+ MEMBER // Bežný člen, len používa systém
240
247
  }
241
248
 
242
249
  // ZJEDNODUŠENÝ Conversation - len metadata, bez duplikátov
243
250
  model Conversation {
244
- id String @id @default(cuid())
251
+ id String @id @default(cuid())
245
252
  name String
246
253
  userId String
247
- isShareable Boolean @default(false)
248
- shareUrl String? @unique
254
+ isShareable Boolean @default(false)
255
+ shareUrl String? @unique
249
256
  sharedAt DateTime?
250
- createdAt DateTime @default(now())
251
- updatedAt DateTime @updatedAt
257
+ createdAt DateTime @default(now())
258
+ updatedAt DateTime @updatedAt
252
259
  summary String?
253
- messagesSinceLastSummary Int? @default(0)
260
+ messagesSinceLastSummary Int? @default(0)
261
+ integrityHash String? // HMAC-SHA256 hash chain - posledný chainHash pre verifikáciu integrity
254
262
  // Relácie
255
263
  answers Answer[]
256
- user User @relation(fields: [userId], references: [id])
264
+ user User @relation(fields: [userId], references: [id])
257
265
  workflowLogs WorkflowLog[]
258
266
  canvasDocuments CanvasDocument[]
259
267
 
@@ -263,29 +271,31 @@ model Conversation {
263
271
 
264
272
  // Hlavný model - všetky správy, bez duplikátov
265
273
  model Answer {
266
- id String @id @default(cuid())
267
- conversationId String
268
- messageId String @unique
269
- role Role
270
- content String
271
- question String?
272
- answer String?
273
- evaluation String?
274
- isWelcome Boolean @default(false)
275
- processingTime Int?
276
- model String?
277
- userId String?
278
- createdAt DateTime @default(now())
279
- updatedAt DateTime @updatedAt
274
+ id String @id @default(cuid())
275
+ conversationId String
276
+ messageId String @unique
277
+ role Role
278
+ content String
279
+ question String?
280
+ answer String?
281
+ evaluation String?
282
+ isWelcome Boolean @default(false)
283
+ processingTime Int?
284
+ model String?
285
+ userId String?
286
+ contentHash String? // HMAC-SHA256 hash obsahu správy (content + role + messageId)
287
+ chainHash String? // Hash chain - zahŕňa predchádzajúci chainHash pre integritu sekvencie
288
+ createdAt DateTime @default(now())
289
+ updatedAt DateTime @updatedAt
280
290
  // Relácie
281
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
282
- user User? @relation(fields: [userId], references: [id])
291
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
292
+ user User? @relation(fields: [userId], references: [id])
283
293
  feedback Feedback?
284
294
  references Reference[]
285
295
  metrics AnswerMetrics?
286
296
  WorkflowLog WorkflowLog[]
287
297
  files MessageFile[]
288
- canvasDocumentId String? // No FK constraint - flexible document management
298
+ canvasDocumentId String? // No FK constraint - flexible document management
289
299
 
290
300
  @@index([conversationId])
291
301
  @@index([messageId])
@@ -413,12 +423,12 @@ model PageView {
413
423
  }
414
424
 
415
425
  model Session {
416
- id String @id @default(cuid())
417
- sessionId String @unique
418
- userId String?
419
- startedAt DateTime @default(now())
420
- endedAt DateTime?
421
- duration Int?
426
+ id String @id @default(cuid())
427
+ sessionId String @unique
428
+ userId String?
429
+ startedAt DateTime @default(now())
430
+ endedAt DateTime?
431
+ duration Int?
422
432
 
423
433
  // ============================================
424
434
  // FIRST-TOUCH ATTRIBUTION
@@ -431,7 +441,7 @@ model Session {
431
441
  firstUtmCampaign String?
432
442
  firstUtmContent String?
433
443
  firstUtmTerm String?
434
- firstTouchAt DateTime? // Timestamp prvého UTM touchpoint
444
+ firstTouchAt DateTime? // Timestamp prvého UTM touchpoint
435
445
 
436
446
  // ============================================
437
447
  // LAST-TOUCH ATTRIBUTION
@@ -444,14 +454,14 @@ model Session {
444
454
  lastUtmCampaign String?
445
455
  lastUtmContent String?
446
456
  lastUtmTerm String?
447
- lastTouchAt DateTime? // Timestamp posledného UTM touchpoint
457
+ lastTouchAt DateTime? // Timestamp posledného UTM touchpoint
448
458
 
449
459
  // ============================================
450
460
  // RELATIONS
451
461
  // ============================================
452
462
 
453
- touchPoints TouchPoint[] // Všetky touchpointy v session
454
- conversions Conversion[] // Všetky konverzie v session
463
+ touchPoints TouchPoint[] // Všetky touchpointy v session
464
+ conversions Conversion[] // Všetky konverzie v session
455
465
 
456
466
  // Existujúce relations
457
467
  pageViews PageView[]
@@ -466,7 +476,6 @@ model Session {
466
476
  @@index([lastUtmCampaign])
467
477
  @@index([firstUtmSource, firstUtmMedium])
468
478
  @@index([lastUtmSource, lastUtmMedium])
469
-
470
479
  // Existujúce indexes
471
480
  @@index([userId])
472
481
  @@index([startedAt])
@@ -479,47 +488,47 @@ model Session {
479
488
  // TouchPoint - každý click s UTM parametrami
480
489
  // Umožňuje multi-touch attribution a customer journey analysis
481
490
  model TouchPoint {
482
- id String @id @default(cuid())
483
- userId String? // Pre prihlásených používateľov
484
- sessionId String // Session tracking (required)
491
+ id String @id @default(cuid())
492
+ userId String? // Pre prihlásených používateľov
493
+ sessionId String // Session tracking (required)
485
494
 
486
495
  // ============================================
487
496
  // UTM PARAMETERS (všetky lowercase!)
488
497
  // ============================================
489
498
  // Best practice: Vždy lowercase, validované pred uložením
490
499
 
491
- utmSource String? // Odkiaľ: "email", "facebook", "google", "linkedin"
492
- utmMedium String? // Typ média: "email", "cpc", "social", "organic", "paid-social"
493
- utmCampaign String? // Názov kampane: "smizany-2026-01", "spring-sale-2026"
494
- utmContent String? // Variant/link identifier: "cta-primary", "banner-top", "hero-button"
495
- utmTerm String? // Keywords (pre paid search): "pravny-asistent", "advokat-ai"
500
+ utmSource String? // Odkiaľ: "email", "facebook", "google", "linkedin"
501
+ utmMedium String? // Typ média: "email", "cpc", "social", "organic", "paid-social"
502
+ utmCampaign String? // Názov kampane: "smizany-2026-01", "spring-sale-2026"
503
+ utmContent String? // Variant/link identifier: "cta-primary", "banner-top", "hero-button"
504
+ utmTerm String? // Keywords (pre paid search): "pravny-asistent", "advokat-ai"
496
505
 
497
506
  // ============================================
498
507
  // CONTEXT & METADATA
499
508
  // ============================================
500
509
 
501
- page String // Ktorá stránka: "/", "/pricing", "/contact"
502
- path String // Celý path s params: "/?utm_source=email&utm_campaign=..."
503
- referrer String? // HTTP Referrer (odkiaľ prišiel)
510
+ page String // Ktorá stránka: "/", "/pricing", "/contact"
511
+ path String // Celý path s params: "/?utm_source=email&utm_campaign=..."
512
+ referrer String? // HTTP Referrer (odkiaľ prišiel)
504
513
 
505
514
  // Device & Browser info (denormalizované pre rýchle queries)
506
- deviceType String? // "mobile", "tablet", "desktop"
507
- browser String? // "Chrome", "Firefox", "Safari"
508
- os String? // "Windows", "macOS", "iOS", "Android"
515
+ deviceType String? // "mobile", "tablet", "desktop"
516
+ browser String? // "Chrome", "Firefox", "Safari"
517
+ os String? // "Windows", "macOS", "iOS", "Android"
509
518
 
510
519
  // Geolocation (anonymizované)
511
- country String? // "SK", "CZ", "US"
512
- city String? // "Bratislava", "Prague"
520
+ country String? // "SK", "CZ", "US"
521
+ city String? // "Bratislava", "Prague"
513
522
 
514
523
  // Timestamp
515
- timestamp DateTime @default(now())
524
+ timestamp DateTime @default(now())
516
525
 
517
526
  // ============================================
518
527
  // RELATIONS
519
528
  // ============================================
520
529
 
521
- session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
522
- user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
530
+ session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
531
+ user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
523
532
 
524
533
  // ============================================
525
534
  // INDEXES (optimalizované pre analytics queries)
@@ -535,38 +544,37 @@ model TouchPoint {
535
544
  @@index([timestamp])
536
545
  @@index([sessionId, timestamp])
537
546
  @@index([userId, timestamp])
538
-
539
547
  @@map("touch_points")
540
548
  }
541
549
 
542
550
  // Conversion - sledovanie konverzií s attribution
543
551
  // Podporuje first-touch, last-touch aj multi-touch attribution
544
552
  model Conversion {
545
- id String @id @default(cuid())
546
- userId String? // Kto konvertoval (nullable pre anonymous conversions)
547
- sessionId String // V ktorej session
553
+ id String @id @default(cuid())
554
+ userId String? // Kto konvertoval (nullable pre anonymous conversions)
555
+ sessionId String // V ktorej session
548
556
 
549
557
  // ============================================
550
558
  // CONVERSION METADATA
551
559
  // ============================================
552
560
 
553
- type ConversionType // Typ konverzie
554
- value Decimal? // Revenue (pre e-commerce/subscriptions)
555
- currency String? @default("EUR")
561
+ type ConversionType // Typ konverzie
562
+ value Decimal? // Revenue (pre e-commerce/subscriptions)
563
+ currency String? @default("EUR")
556
564
 
557
565
  // Optional metadata
558
- metadata Json? // Stripe subscription ID, product info, etc.
566
+ metadata Json? // Stripe subscription ID, product info, etc.
559
567
 
560
568
  // ============================================
561
569
  // FIRST-TOUCH ATTRIBUTION (40% credit)
562
570
  // ============================================
563
571
  // Prvý UTM touchpoint v session = awareness channel
564
572
 
565
- firstUtmSource String? // Odkiaľ prišiel prvýkrát
566
- firstUtmMedium String? // Akým médiom prvýkrát
567
- firstUtmCampaign String? // Ktorá kampaň priniesla awareness
568
- firstUtmContent String? // Ktorý konkrétny link/banner
569
- firstUtmTerm String? // Ktoré keywords (pre paid search)
573
+ firstUtmSource String? // Odkiaľ prišiel prvýkrát
574
+ firstUtmMedium String? // Akým médiom prvýkrát
575
+ firstUtmCampaign String? // Ktorá kampaň priniesla awareness
576
+ firstUtmContent String? // Ktorý konkrétny link/banner
577
+ firstUtmTerm String? // Ktoré keywords (pre paid search)
570
578
  firstTouchAt DateTime? // Kedy bol prvý touchpoint
571
579
 
572
580
  // ============================================
@@ -574,11 +582,11 @@ model Conversion {
574
582
  // ============================================
575
583
  // Posledný UTM touchpoint pred konverziou = conversion channel
576
584
 
577
- lastUtmSource String? // Odkiaľ prišiel naposledy
578
- lastUtmMedium String? // Akým médiom naposledy
579
- lastUtmCampaign String? // Ktorá kampaň priniesla konverziu
580
- lastUtmContent String? // Ktorý konkrétny link/banner
581
- lastUtmTerm String? // Ktoré keywords (pre paid search)
585
+ lastUtmSource String? // Odkiaľ prišiel naposledy
586
+ lastUtmMedium String? // Akým médiom naposledy
587
+ lastUtmCampaign String? // Ktorá kampaň priniesla konverziu
588
+ lastUtmContent String? // Ktorý konkrétny link/banner
589
+ lastUtmTerm String? // Ktoré keywords (pre paid search)
582
590
  lastTouchAt DateTime? // Kedy bol posledný touchpoint
583
591
 
584
592
  // ============================================
@@ -586,24 +594,24 @@ model Conversion {
586
594
  // ============================================
587
595
  // Všetky touchpointy medzi prvým a posledným
588
596
 
589
- touchPointIds String[] // Array of TouchPoint IDs (pre detailnú analýzu)
590
- touchPointCount Int? // Počet touchpoints v customer journey
597
+ touchPointIds String[] // Array of TouchPoint IDs (pre detailnú analýzu)
598
+ touchPointCount Int? // Počet touchpoints v customer journey
591
599
 
592
600
  // ============================================
593
601
  // CALCULATED FIELDS
594
602
  // ============================================
595
603
 
596
- journeyDuration Int? // Čas od prvého touchpoint po konverziu (sekundy)
604
+ journeyDuration Int? // Čas od prvého touchpoint po konverziu (sekundy)
597
605
 
598
606
  // Timestamp
599
- timestamp DateTime @default(now())
607
+ timestamp DateTime @default(now())
600
608
 
601
609
  // ============================================
602
610
  // RELATIONS
603
611
  // ============================================
604
612
 
605
- session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
606
- user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
613
+ session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
614
+ user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
607
615
 
608
616
  // ============================================
609
617
  // INDEXES
@@ -613,25 +621,21 @@ model Conversion {
613
621
  @@index([sessionId])
614
622
  @@index([type])
615
623
  @@index([timestamp])
616
-
617
624
  // First-touch attribution indexes
618
625
  @@index([firstUtmSource])
619
626
  @@index([firstUtmMedium])
620
627
  @@index([firstUtmCampaign])
621
628
  @@index([firstUtmCampaign, type])
622
629
  @@index([firstUtmSource, firstUtmMedium])
623
-
624
630
  // Last-touch attribution indexes
625
631
  @@index([lastUtmSource])
626
632
  @@index([lastUtmMedium])
627
633
  @@index([lastUtmCampaign])
628
634
  @@index([lastUtmCampaign, type])
629
635
  @@index([lastUtmSource, lastUtmMedium])
630
-
631
636
  // Analytics indexes
632
637
  @@index([type, timestamp])
633
638
  @@index([value])
634
-
635
639
  @@map("conversions")
636
640
  }
637
641
 
@@ -641,28 +645,28 @@ model Conversion {
641
645
 
642
646
  // Stripe Customer - polymorphic relation (User OR Organization)
643
647
  model StripeCustomer {
644
- id String @id @default(cuid())
648
+ id String @id @default(cuid())
645
649
 
646
650
  // ✅ Polymorphic relation - buď User (B2C) alebo Organization (B2B)
647
- userId String? @unique // B2C: Individuálny používateľ
648
- organizationId String? @unique // B2B: Organizácia
651
+ userId String? @unique // B2C: Individuálny používateľ
652
+ organizationId String? @unique // B2B: Organizácia
649
653
 
650
- stripeCustomerId String @unique
651
- email String
652
- name String?
654
+ stripeCustomerId String @unique
655
+ email String
656
+ name String?
653
657
 
654
658
  // Billing details (Stripe Address & Tax IDs)
655
- billingAddress Json? // Stripe Address object
656
- taxIds Json? // Stripe Tax IDs array (VAT, etc.)
659
+ billingAddress Json? // Stripe Address object
660
+ taxIds Json? // Stripe Tax IDs array (VAT, etc.)
657
661
 
658
- createdAt DateTime @default(now())
659
- updatedAt DateTime @updatedAt
662
+ createdAt DateTime @default(now())
663
+ updatedAt DateTime @updatedAt
660
664
 
661
665
  // Relations
662
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
663
- organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
664
- subscriptions StripeSubscription[]
665
- payments StripePayment[]
666
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
667
+ organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
668
+ subscriptions StripeSubscription[]
669
+ payments StripePayment[]
666
670
 
667
671
  @@index([stripeCustomerId])
668
672
  @@index([userId])
@@ -672,37 +676,37 @@ model StripeCustomer {
672
676
 
673
677
  // Stripe Subscription - sledovanie predplatného (User alebo Organization)
674
678
  model StripeSubscription {
675
- id String @id @default(cuid())
679
+ id String @id @default(cuid())
676
680
 
677
681
  // ✅ FIX: customerId = FK na StripeCustomer.id (nie stripeCustomerId!)
678
- customerId String // FK na StripeCustomer.id
679
- stripeCustomerId String // Stripe ID (pre logging/redundancia)
680
- stripeSubscriptionId String @unique
682
+ customerId String // FK na StripeCustomer.id
683
+ stripeCustomerId String // Stripe ID (pre logging/redundancia)
684
+ stripeSubscriptionId String @unique
681
685
  stripePriceId String
682
686
  stripeProductId String
683
687
 
684
- status SubscriptionStatus
685
- tier SubscriptionTier @default(FREE)
686
- billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
688
+ status SubscriptionStatus
689
+ tier SubscriptionTier @default(FREE)
690
+ billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
687
691
 
688
- currentPeriodStart DateTime
689
- currentPeriodEnd DateTime
690
- cancelAtPeriodEnd Boolean @default(false)
691
- canceledAt DateTime?
692
+ currentPeriodStart DateTime
693
+ currentPeriodEnd DateTime
694
+ cancelAtPeriodEnd Boolean @default(false)
695
+ canceledAt DateTime?
692
696
 
693
- trialStart DateTime?
694
- trialEnd DateTime?
697
+ trialStart DateTime?
698
+ trialEnd DateTime?
695
699
 
696
700
  // Additional fields
697
- quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
698
- defaultPaymentMethodId String? // Stripe payment method ID
701
+ quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
702
+ defaultPaymentMethodId String? // Stripe payment method ID
699
703
 
700
- metadata Json? // Dodatočné Stripe metadata
701
- createdAt DateTime @default(now())
702
- updatedAt DateTime @updatedAt
704
+ metadata Json? // Dodatočné Stripe metadata
705
+ createdAt DateTime @default(now())
706
+ updatedAt DateTime @updatedAt
703
707
 
704
708
  // ✅ FIX: Relácia na customerId (interné ID), nie stripeCustomerId (Stripe ID)
705
- customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
709
+ customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
706
710
 
707
711
  @@index([stripeSubscriptionId])
708
712
  @@index([customerId])
@@ -715,14 +719,14 @@ model StripeSubscription {
715
719
 
716
720
  // Stripe Event - prevencia duplicitného spracovania webhookov (idempotencia)
717
721
  model StripeEvent {
718
- id String @id @default(cuid())
719
- eventId String @unique // Stripe event ID
720
- type String // Typ eventu (napr. 'checkout.session.completed')
721
- data Json // Úplné event data pre debugging
722
- processed Boolean @default(false)
722
+ id String @id @default(cuid())
723
+ eventId String @unique // Stripe event ID
724
+ type String // Typ eventu (napr. 'checkout.session.completed')
725
+ data Json // Úplné event data pre debugging
726
+ processed Boolean @default(false)
723
727
  processedAt DateTime?
724
- error String? // Uloženie chýb pri spracovaní
725
- createdAt DateTime @default(now())
728
+ error String? // Uloženie chýb pri spracovaní
729
+ createdAt DateTime @default(now())
726
730
 
727
731
  @@index([eventId])
728
732
  @@index([processed])
@@ -732,28 +736,28 @@ model StripeEvent {
732
736
 
733
737
  // Stripe Payment - sledovanie jednotlivých platieb (voliteľné, pre detailnú analytiku)
734
738
  model StripePayment {
735
- id String @id @default(cuid())
739
+ id String @id @default(cuid())
736
740
 
737
741
  // ✅ FIX: Pridaná relácia na StripeCustomer
738
- customerId String
739
- stripePaymentId String @unique
740
- stripeCustomerId String // Redundantné, ale OK pre logging
742
+ customerId String
743
+ stripePaymentId String @unique
744
+ stripeCustomerId String // Redundantné, ale OK pre logging
741
745
 
742
- amount Int // Suma v centoch
743
- currency String @default("eur")
744
- status String // succeeded, failed, pending
745
- paymentMethod String? // card, sepa_debit, atď.
746
- description String?
746
+ amount Int // Suma v centoch
747
+ currency String @default("eur")
748
+ status String // succeeded, failed, pending
749
+ paymentMethod String? // card, sepa_debit, atď.
750
+ description String?
747
751
 
748
752
  // Invoice info
749
- invoiceId String? // Stripe Invoice ID
750
- invoiceUrl String? // URL na faktúru
753
+ invoiceId String? // Stripe Invoice ID
754
+ invoiceUrl String? // URL na faktúru
751
755
 
752
- metadata Json?
753
- createdAt DateTime @default(now())
756
+ metadata Json?
757
+ createdAt DateTime @default(now())
754
758
 
755
759
  // ✅ Relácia na StripeCustomer
756
- customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
760
+ customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
757
761
 
758
762
  @@index([stripePaymentId])
759
763
  @@index([customerId])
@@ -795,35 +799,35 @@ enum ApprovalStatus {
795
799
 
796
800
  // Stripe: Status predplatného
797
801
  enum SubscriptionStatus {
798
- ACTIVE // Predplatné je aktívne
799
- CANCELED // Predplatné zrušené
800
- INCOMPLETE // Iniciálna platba zlyhala
802
+ ACTIVE // Predplatné je aktívne
803
+ CANCELED // Predplatné zrušené
804
+ INCOMPLETE // Iniciálna platba zlyhala
801
805
  INCOMPLETE_EXPIRED // Neúplné predplatné expirované
802
- PAST_DUE // Platba zlyhala, retry prebieha
803
- TRIALING // V skúšobnom období
804
- UNPAID // Platba zlyhala, žiadny retry
805
- PAUSED // Predplatné pozastavené (Stripe feature)
806
+ PAST_DUE // Platba zlyhala, retry prebieha
807
+ TRIALING // V skúšobnom období
808
+ UNPAID // Platba zlyhala, žiadny retry
809
+ PAUSED // Predplatné pozastavené (Stripe feature)
806
810
  }
807
811
 
808
812
  // Customer type: Individuálny vs SZČO
809
813
  enum CustomerType {
810
- INDIVIDUAL // Fyzická osoba (bez IČO)
811
- SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
814
+ INDIVIDUAL // Fyzická osoba (bez IČO)
815
+ SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
812
816
  }
813
817
 
814
818
  // Stripe: Tier predplatného
815
819
  enum SubscriptionTier {
816
- FREE // Free tier (4 messages/month)
817
- LAWYER // Lawyer tier (1000 messages/month, €29/month, 1 user)
818
- LAWYER_PRO // Lawyer Pro tier (3000 messages/month, €49/month, 1 user) - RECOMMENDED
819
- LAW_FIRM // Law Firm tier (10000 messages/month, €129/month, up to 5 users)
820
- ENTERPRISE // Enterprise tier (unlimited messages, unlimited users, custom pricing)
820
+ FREE // Free tier (10 messages/month)
821
+ LAWYER // Lawyer tier (1000 messages/month, €29/month, 1 user)
822
+ LAWYER_PRO // Lawyer Pro tier (3000 messages/month, €49/month, 1 user) - RECOMMENDED
823
+ LAW_FIRM // Law Firm tier (10000 messages/month, €129/month, up to 5 users)
824
+ ENTERPRISE // Enterprise tier (unlimited messages, unlimited users, custom pricing)
821
825
  }
822
826
 
823
827
  // Stripe: Billing interval
824
828
  enum BillingInterval {
825
- MONTHLY // Monthly billing
826
- YEARLY // Yearly billing (17% discount)
829
+ MONTHLY // Monthly billing
830
+ YEARLY // Yearly billing (17% discount)
827
831
  }
828
832
 
829
833
  // Canvas Document Status
@@ -835,16 +839,16 @@ enum DocumentStatus {
835
839
 
836
840
  // Conversion types for UTM attribution tracking
837
841
  enum ConversionType {
838
- REGISTRATION // Nová registrácia
839
- SUBSCRIPTION // Začiatok predplatného (FREE → PAID)
840
- UPGRADE // Upgrade tieru (LAWYER → LAWYER_PRO)
841
- RENEWAL // Obnovenie predplatného
842
- TRIAL_START // Začiatok trial periodu
843
- PURCHASE // Jednorazový nákup
844
- LEAD // Vyplnenie kontaktného formulára
845
- DEMO_REQUEST // Žiadosť o demo
846
- DOWNLOAD // Stiahnutie resourceu
847
- FORM_SUBMIT // Iné formuláre
842
+ REGISTRATION // Nová registrácia
843
+ SUBSCRIPTION // Začiatok predplatného (FREE → PAID)
844
+ UPGRADE // Upgrade tieru (LAWYER → LAWYER_PRO)
845
+ RENEWAL // Obnovenie predplatného
846
+ TRIAL_START // Začiatok trial periodu
847
+ PURCHASE // Jednorazový nákup
848
+ LEAD // Vyplnenie kontaktného formulára
849
+ DEMO_REQUEST // Žiadosť o demo
850
+ DOWNLOAD // Stiahnutie resourceu
851
+ FORM_SUBMIT // Iné formuláre
848
852
  }
849
853
 
850
854
  // ============================================
@@ -971,12 +975,12 @@ enum StepType {
971
975
 
972
976
  // Email verification tokens
973
977
  model VerificationToken {
974
- id String @id @default(cuid())
975
- userId String
976
- token String @unique
977
- expires DateTime
978
- createdAt DateTime @default(now())
979
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
978
+ id String @id @default(cuid())
979
+ userId String
980
+ token String @unique
981
+ expires DateTime
982
+ createdAt DateTime @default(now())
983
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
980
984
 
981
985
  @@index([token])
982
986
  @@index([userId])
@@ -985,38 +989,53 @@ model VerificationToken {
985
989
 
986
990
  // Password reset tokens
987
991
  model PasswordResetToken {
988
- id String @id @default(cuid())
989
- userId String
990
- token String @unique
991
- expires DateTime
992
- createdAt DateTime @default(now())
993
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
992
+ id String @id @default(cuid())
993
+ userId String
994
+ token String @unique
995
+ expires DateTime
996
+ createdAt DateTime @default(now())
997
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
994
998
 
995
999
  @@index([token])
996
1000
  @@index([userId])
997
1001
  @@index([expires])
998
1002
  }
999
1003
 
1004
+ // Phone OTP verification (BulkGate)
1005
+ model PhoneOtp {
1006
+ id String @id @default(cuid())
1007
+ userId String
1008
+ phone String
1009
+ bulkgateId String @unique // OTP ID from BulkGate API response
1010
+ expiresAt DateTime
1011
+ createdAt DateTime @default(now())
1012
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1013
+
1014
+ @@index([userId])
1015
+ @@index([bulkgateId])
1016
+ @@index([expiresAt])
1017
+ }
1018
+
1000
1019
  // ============================================
1001
1020
  // CANVAS DOCUMENT MODELS
1002
1021
  // ============================================
1003
1022
 
1004
1023
  model CanvasDocument {
1005
- id String @id @default(cuid())
1024
+ id String @id @default(cuid())
1006
1025
  userId String
1007
1026
  title String
1008
- status DocumentStatus @default(DRAFT)
1009
- createdAt DateTime @default(now())
1010
- updatedAt DateTime @updatedAt
1027
+ status DocumentStatus @default(DRAFT)
1028
+ createdAt DateTime @default(now())
1029
+ updatedAt DateTime @updatedAt
1011
1030
  originConversationId String?
1012
- originAnswerId String? // No FK constraint - async save reference
1013
- currentVersionId String? @unique
1031
+ originAnswerId String? // No FK constraint - async save reference
1032
+ currentVersionId String? @unique
1014
1033
 
1015
1034
  // Relations
1016
- user User @relation(fields: [userId], references: [id], onDelete: Restrict)
1017
- originConversation Conversation? @relation(fields: [originConversationId], references: [id], onDelete: SetNull)
1018
- currentVersion CanvasDocumentVersion? @relation("CurrentVersion", fields: [currentVersionId], references: [id], onDelete: SetNull)
1019
- versions CanvasDocumentVersion[] @relation("DocumentVersions")
1035
+ user User @relation(fields: [userId], references: [id], onDelete: Restrict)
1036
+ originConversation Conversation? @relation(fields: [originConversationId], references: [id], onDelete: SetNull)
1037
+ currentVersion CanvasDocumentVersion? @relation("CurrentVersion", fields: [currentVersionId], references: [id], onDelete: SetNull)
1038
+ versions CanvasDocumentVersion[] @relation("DocumentVersions")
1020
1039
 
1021
1040
  @@index([userId])
1022
1041
  @@index([originConversationId])
@@ -1025,12 +1044,12 @@ model CanvasDocument {
1025
1044
  }
1026
1045
 
1027
1046
  model CanvasDocumentVersion {
1028
- id String @id @default(cuid())
1047
+ id String @id @default(cuid())
1029
1048
  documentId String
1030
1049
  versionNumber Int
1031
1050
  title String
1032
1051
  markdownContent String
1033
- createdAt DateTime @default(now())
1052
+ createdAt DateTime @default(now())
1034
1053
  createdBy String
1035
1054
  regenerationPrompt String?
1036
1055
  parentVersionId String?
@@ -1055,34 +1074,34 @@ model CanvasDocumentVersion {
1055
1074
  // Main folder entity with hierarchy support via parentId
1056
1075
  // Uses materialized path pattern for efficient hierarchy queries
1057
1076
  model Folder {
1058
- id String @id @default(cuid())
1077
+ id String @id @default(cuid())
1059
1078
  name String
1060
1079
  description String?
1061
- color String? @default("#6366f1")
1062
- icon String? @default("folder")
1080
+ color String? @default("#6366f1")
1081
+ icon String? @default("folder")
1063
1082
 
1064
1083
  // Ownership (always user-owned)
1065
- ownerId String
1084
+ ownerId String
1066
1085
 
1067
1086
  // Hierarchy
1068
1087
  parentId String?
1069
- rootFolderId String? // NULL for root folders, points to top-level ancestor
1070
- path String @default("/") // Materialized path: "/parentId/grandparentId/..."
1071
- depth Int @default(0)
1088
+ rootFolderId String? // NULL for root folders, points to top-level ancestor
1089
+ path String @default("/") // Materialized path: "/parentId/grandparentId/..."
1090
+ depth Int @default(0)
1072
1091
 
1073
1092
  // Metadata
1074
- sortOrder Int @default(0)
1075
- isArchived Boolean @default(false)
1076
- createdAt DateTime @default(now())
1077
- updatedAt DateTime @updatedAt
1093
+ sortOrder Int @default(0)
1094
+ isArchived Boolean @default(false)
1095
+ createdAt DateTime @default(now())
1096
+ updatedAt DateTime @updatedAt
1078
1097
 
1079
1098
  // Relations
1080
- owner User @relation("FolderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
1081
- parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
1082
- children Folder[] @relation("FolderHierarchy")
1083
- items FolderItem[]
1084
- activities FolderActivity[]
1085
- shares FolderShare[]
1099
+ owner User @relation("FolderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
1100
+ parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
1101
+ children Folder[] @relation("FolderHierarchy")
1102
+ items FolderItem[]
1103
+ activities FolderActivity[]
1104
+ shares FolderShare[]
1086
1105
 
1087
1106
  @@index([ownerId])
1088
1107
  @@index([parentId])
@@ -1095,12 +1114,12 @@ model Folder {
1095
1114
  // Links entities (conversations, documents, etc.) to folders
1096
1115
  // Polymorphic: entityType + entityId point to any supported entity
1097
1116
  model FolderItem {
1098
- id String @id @default(cuid())
1099
- folderId String
1117
+ id String @id @default(cuid())
1118
+ folderId String
1100
1119
 
1101
1120
  // Polymorphic reference
1102
- entityType FolderItemType
1103
- entityId String
1121
+ entityType FolderItemType
1122
+ entityId String
1104
1123
 
1105
1124
  // Optional metadata override
1106
1125
  displayName String?
@@ -1110,14 +1129,14 @@ model FolderItem {
1110
1129
  rootFolderId String?
1111
1130
 
1112
1131
  // Tracking
1113
- addedById String
1114
- sortOrder Int @default(0)
1115
- createdAt DateTime @default(now())
1116
- updatedAt DateTime @updatedAt
1132
+ addedById String
1133
+ sortOrder Int @default(0)
1134
+ createdAt DateTime @default(now())
1135
+ updatedAt DateTime @updatedAt
1117
1136
 
1118
1137
  // Relations
1119
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1120
- addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
1138
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1139
+ addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
1121
1140
 
1122
1141
  @@unique([folderId, entityType, entityId])
1123
1142
  @@index([folderId])
@@ -1129,25 +1148,25 @@ model FolderItem {
1129
1148
  // Timeline entries for folders (system events + user messages)
1130
1149
  // Opening ANY folder shows ALL activities from the entire folder tree
1131
1150
  model FolderActivity {
1132
- id String @id @default(cuid())
1133
- folderId String
1151
+ id String @id @default(cuid())
1152
+ folderId String
1134
1153
 
1135
1154
  // Denormalized for cross-folder timeline queries
1136
- rootFolderId String?
1155
+ rootFolderId String?
1137
1156
 
1138
1157
  // Activity details
1139
1158
  activityType FolderActivityType
1140
1159
  userId String
1141
- content String? // For USER_MESSAGE, USER_NOTE types
1142
- metadata Json? // For system event details
1143
- relatedItemId String? // Optional reference to FolderItem
1160
+ content String? // For USER_MESSAGE, USER_NOTE types
1161
+ metadata Json? // For system event details
1162
+ relatedItemId String? // Optional reference to FolderItem
1144
1163
 
1145
1164
  // Timestamp
1146
- createdAt DateTime @default(now())
1165
+ createdAt DateTime @default(now())
1147
1166
 
1148
1167
  // Relations
1149
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1150
- user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
1168
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1169
+ user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
1151
1170
 
1152
1171
  @@index([folderId])
1153
1172
  @@index([rootFolderId])
@@ -1160,26 +1179,26 @@ model FolderActivity {
1160
1179
  // Sharing permissions for folders
1161
1180
  // Can share with individual user OR entire organization (not both)
1162
1181
  model FolderShare {
1163
- id String @id @default(cuid())
1164
- folderId String
1182
+ id String @id @default(cuid())
1183
+ folderId String
1165
1184
 
1166
1185
  // Share target (either user OR organization, enforced by application logic)
1167
1186
  userId String?
1168
1187
  organizationId String?
1169
1188
 
1170
1189
  // Permission level
1171
- permission FolderPermission @default(VIEW)
1190
+ permission FolderPermission @default(VIEW)
1172
1191
 
1173
1192
  // Sharing metadata
1174
- sharedById String
1175
- sharedAt DateTime @default(now())
1176
- expiresAt DateTime?
1193
+ sharedById String
1194
+ sharedAt DateTime @default(now())
1195
+ expiresAt DateTime?
1177
1196
 
1178
1197
  // Relations
1179
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1180
- user User? @relation("FolderShareUser", fields: [userId], references: [id], onDelete: Cascade)
1181
- organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
1182
- sharedBy User @relation("FolderShareCreator", fields: [sharedById], references: [id], onDelete: Restrict)
1198
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1199
+ user User? @relation("FolderShareUser", fields: [userId], references: [id], onDelete: Cascade)
1200
+ organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
1201
+ sharedBy User @relation("FolderShareCreator", fields: [sharedById], references: [id], onDelete: Restrict)
1183
1202
 
1184
1203
  // Partial unique indexes for nullable columns
1185
1204
  @@unique([folderId, userId])
@@ -0,0 +1,23 @@
1
+ -- Multi-Brand Support: Allow same email to exist across different brands (smartlex, justi)
2
+ -- Existing data gets brand = 'smartlex' via DEFAULT value
3
+
4
+ -- DropIndex
5
+ DROP INDEX "Account_provider_providerAccountId_key";
6
+
7
+ -- DropIndex
8
+ DROP INDEX "User_email_key";
9
+
10
+ -- AlterTable: Add brand column to Account with default 'smartlex'
11
+ ALTER TABLE "Account" ADD COLUMN "brand" TEXT NOT NULL DEFAULT 'smartlex';
12
+
13
+ -- AlterTable: Add brand column to User with default 'smartlex'
14
+ ALTER TABLE "User" ADD COLUMN "brand" TEXT NOT NULL DEFAULT 'smartlex';
15
+
16
+ -- CreateIndex: Compound unique on Account (provider + providerAccountId + brand)
17
+ CREATE UNIQUE INDEX "Account_provider_providerAccountId_brand_key" ON "Account"("provider", "providerAccountId", "brand");
18
+
19
+ -- CreateIndex: Index on User.brand for fast filtering
20
+ CREATE INDEX "User_brand_idx" ON "User"("brand");
21
+
22
+ -- CreateIndex: Compound unique on User (email + brand) - allows same email in different brands
23
+ CREATE UNIQUE INDEX "User_email_brand_key" ON "User"("email", "brand");
@@ -0,0 +1,9 @@
1
+ -- AlterTable: Add integrity hashing fields for HMAC-SHA256 conversation verification
2
+ -- contentHash: HMAC-SHA256 hash of message content (content + role + messageId)
3
+ -- chainHash: Hash chain linking to previous message for sequence integrity
4
+ -- integrityHash: Last chainHash for full conversation integrity verification
5
+
6
+ ALTER TABLE "Answer" ADD COLUMN "contentHash" TEXT;
7
+ ALTER TABLE "Answer" ADD COLUMN "chainHash" TEXT;
8
+
9
+ ALTER TABLE "Conversation" ADD COLUMN "integrityHash" TEXT;
@@ -1,17 +0,0 @@
1
- -- Add LEGAL_OPINION to ReferenceType enum (if not exists)
2
- DO $$
3
- BEGIN
4
- IF NOT EXISTS (
5
- SELECT 1 FROM pg_enum
6
- WHERE enumtypid = (SELECT oid FROM pg_type WHERE typname = 'ReferenceType')
7
- AND enumlabel = 'LEGAL_OPINION'
8
- ) THEN
9
- ALTER TYPE "ReferenceType" ADD VALUE 'LEGAL_OPINION';
10
- END IF;
11
- END
12
- $$;
13
-
14
- -- AlterTable: Add new profile fields to User (if not exist)
15
- ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "phone" TEXT;
16
- ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "occupation" TEXT;
17
- ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "preferredLanguage" TEXT DEFAULT 'sk';
@@ -1,5 +0,0 @@
1
- -- AlterTable
2
- ALTER TABLE "Organization" ALTER COLUMN "messageLimit" SET DEFAULT 4;
3
-
4
- -- AlterTable
5
- ALTER TABLE "User" ALTER COLUMN "messageCount" SET DEFAULT 4;