@danielcok17/prisma-db 1.10.1 → 1.12.0

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.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "Shared Prisma schema for Legal AI applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/prisma/app.prisma CHANGED
@@ -23,77 +23,105 @@ 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(10) // ✅ OPRAVENÉ z 100 na 10
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)
72
+ // ✨ UTM TRACKING & ATTRIBUTION FIELDS
73
+ // First-visit attribution (celý lifecycle)
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
80
+ // Registration attribution
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
70
86
  // Relations
71
- approvalRequest UserApprovalRequest?
72
- stripeCustomer StripeCustomer? // ✨ B2C Stripe customer
73
- ownedOrganizations Organization[] @relation("OrganizationOwner")
74
- organizationMembers OrganizationMember[]
75
- createdInvites OrganizationInvite[] @relation("InviteCreator")
76
- accounts Account[]
77
- answers Answer[]
78
- conversations Conversation[]
79
- feedbacks Feedback[]
80
- pageViews PageView[]
81
- sessions Session[]
82
- workflowLogs WorkflowLog[]
83
- verificationTokens VerificationToken[]
84
- passwordResetTokens PasswordResetToken[]
85
- canvasDocuments CanvasDocument[]
86
- canvasDocumentVersions CanvasDocumentVersion[]
87
+ approvalRequest UserApprovalRequest?
88
+ stripeCustomer StripeCustomer? // ✨ B2C Stripe customer
89
+ ownedOrganizations Organization[] @relation("OrganizationOwner")
90
+ organizationMembers OrganizationMember[]
91
+ createdInvites OrganizationInvite[] @relation("InviteCreator")
92
+ accounts Account[]
93
+ answers Answer[]
94
+ conversations Conversation[]
95
+ feedbacks Feedback[]
96
+ pageViews PageView[]
97
+ sessions Session[]
98
+ workflowLogs WorkflowLog[]
99
+ verificationTokens VerificationToken[]
100
+ passwordResetTokens PasswordResetToken[]
101
+ canvasDocuments CanvasDocument[]
102
+ canvasDocumentVersions CanvasDocumentVersion[]
87
103
  // Folder system relations
88
- ownedFolders Folder[] @relation("FolderOwner")
89
- addedFolderItems FolderItem[] @relation("FolderItemAdder")
90
- folderActivities FolderActivity[] @relation("FolderActivityUser")
91
- folderSharesReceived FolderShare[] @relation("FolderShareUser")
92
- createdFolderShares FolderShare[] @relation("FolderShareCreator")
93
-
104
+ ownedFolders Folder[] @relation("FolderOwner")
105
+ addedFolderItems FolderItem[] @relation("FolderItemAdder")
106
+ folderActivities FolderActivity[] @relation("FolderActivityUser")
107
+ folderSharesReceived FolderShare[] @relation("FolderShareUser")
108
+ createdFolderShares FolderShare[] @relation("FolderShareCreator")
109
+ // UTM tracking relations
110
+ touchPoints TouchPoint[]
111
+ conversions Conversion[]
112
+
113
+ // Multi-brand: compound unique allows same email across brands
114
+ @@unique([email, brand])
115
+ @@index([brand])
94
116
  @@index([customerType])
95
117
  @@index([companyNumber])
96
118
  @@index([email, customerType])
119
+ // UTM tracking indexes
120
+ @@index([firstUtmSource])
121
+ @@index([firstUtmCampaign])
122
+ @@index([registrationUtmSource])
123
+ @@index([registrationUtmCampaign])
124
+ @@index([firstVisitAt])
97
125
  }
98
126
 
99
127
  // Nový model pre žiadosti o schválenie
@@ -118,43 +146,43 @@ model UserApprovalRequest {
118
146
 
119
147
  // Organization = Právna kancelária / Firma
120
148
  model Organization {
121
- id String @id @default(cuid())
122
- name String // "Advokátska kancelária XYZ s.r.o."
149
+ id String @id @default(cuid())
150
+ name String // "Advokátska kancelária XYZ s.r.o."
123
151
 
124
152
  // Billing & Legal Information
125
- companyNumber String? // IČO (Identifikačné číslo organizácie)
126
- vatNumber String? // IČ DPH (VAT Number)
127
- taxNumber String? // DIČ (Daňové identifikačné číslo)
128
- legalForm String? // "s.r.o.", "a.s.", "v.o.s.", "k.s.", "SZČO"
129
- billingEmail String
153
+ companyNumber String? // IČO (Identifikačné číslo organizácie)
154
+ vatNumber String? // IČ DPH (VAT Number)
155
+ taxNumber String? // DIČ (Daňové identifikačné číslo)
156
+ legalForm String? // "s.r.o.", "a.s.", "v.o.s.", "k.s.", "SZČO"
157
+ billingEmail String
130
158
 
131
159
  // Address
132
- street String?
133
- city String?
134
- postalCode String?
135
- country String @default("SK")
160
+ street String?
161
+ city String?
162
+ postalCode String?
163
+ country String @default("SK")
136
164
 
137
165
  // Subscription (B2B)
138
166
  subscriptionTier SubscriptionTier @default(FREE) // Tier organizácie (cache)
139
- messageCount Int @default(0) // Spoločný pool správ pre všetkých členov
140
- messageLimit Int @default(100) // Limit správ podľa tieru
141
- messageCountResetAt DateTime? // Kedy sa má resetovať pool
142
- adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
167
+ messageCount Int @default(0) // Spoločný pool správ pre všetkých členov
168
+ messageLimit Int @default(100) // Limit správ podľa tieru
169
+ messageCountResetAt DateTime? // Kedy sa má resetovať pool
170
+ adminGrantExpiresAt DateTime? // Kedy admin grant expiruje
143
171
 
144
172
  // Settings
145
- isActive Boolean @default(true)
146
- maxMembers Int @default(5) // Max počet členov podľa tieru
173
+ isActive Boolean @default(true)
174
+ maxMembers Int @default(5) // Max počet členov podľa tieru
147
175
 
148
176
  // Relations
149
- owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
177
+ owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
150
178
  ownerId String
151
179
  members OrganizationMember[]
152
180
  invites OrganizationInvite[]
153
181
  stripeCustomer StripeCustomer?
154
182
  folderShares FolderShare[]
155
183
 
156
- createdAt DateTime @default(now())
157
- updatedAt DateTime @updatedAt
184
+ createdAt DateTime @default(now())
185
+ updatedAt DateTime @updatedAt
158
186
 
159
187
  @@index([ownerId])
160
188
  @@index([vatNumber])
@@ -165,14 +193,14 @@ model Organization {
165
193
 
166
194
  // Many-to-Many: User ↔ Organization
167
195
  model OrganizationMember {
168
- id String @id @default(cuid())
196
+ id String @id @default(cuid())
169
197
  organizationId String
170
198
  userId String
171
- role MemberRole @default(MEMBER)
172
- joinedAt DateTime @default(now())
199
+ role MemberRole @default(MEMBER)
200
+ joinedAt DateTime @default(now())
173
201
 
174
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
175
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
202
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
203
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
176
204
 
177
205
  @@unique([organizationId, userId])
178
206
  @@index([organizationId])
@@ -182,23 +210,23 @@ model OrganizationMember {
182
210
 
183
211
  // Organization Invite System
184
212
  model OrganizationInvite {
185
- id String @id @default(cuid())
213
+ id String @id @default(cuid())
186
214
  organizationId String
187
- token String @unique
188
- email String? // Optional: specific email invite
189
- role MemberRole @default(MEMBER)
215
+ token String @unique
216
+ email String? // Optional: specific email invite
217
+ role MemberRole @default(MEMBER)
190
218
  createdBy String
191
219
  expiresAt DateTime
192
- maxUses Int @default(1) // For multi-use invites
193
- currentUses Int @default(0)
194
- isActive Boolean @default(true)
195
- usedAt DateTime? // Last usage timestamp
196
- usedBy String? // Last user who used it
197
- createdAt DateTime @default(now())
198
- updatedAt DateTime @updatedAt
220
+ maxUses Int @default(1) // For multi-use invites
221
+ currentUses Int @default(0)
222
+ isActive Boolean @default(true)
223
+ usedAt DateTime? // Last usage timestamp
224
+ usedBy String? // Last user who used it
225
+ createdAt DateTime @default(now())
226
+ updatedAt DateTime @updatedAt
199
227
 
200
- organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
201
- creator User @relation("InviteCreator", fields: [createdBy], references: [id])
228
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
229
+ creator User @relation("InviteCreator", fields: [createdBy], references: [id])
202
230
 
203
231
  @@index([token])
204
232
  @@index([organizationId])
@@ -207,26 +235,26 @@ model OrganizationInvite {
207
235
  }
208
236
 
209
237
  enum MemberRole {
210
- OWNER // Zakladateľ/vlastník, plný prístup k billing a settings
211
- ADMIN // Administrátor, môže pridávať/odoberať členov
212
- MEMBER // Bežný člen, len používa systém
238
+ OWNER // Zakladateľ/vlastník, plný prístup k billing a settings
239
+ ADMIN // Administrátor, môže pridávať/odoberať členov
240
+ MEMBER // Bežný člen, len používa systém
213
241
  }
214
242
 
215
243
  // ZJEDNODUŠENÝ Conversation - len metadata, bez duplikátov
216
244
  model Conversation {
217
- id String @id @default(cuid())
245
+ id String @id @default(cuid())
218
246
  name String
219
247
  userId String
220
- isShareable Boolean @default(false)
221
- shareUrl String? @unique
248
+ isShareable Boolean @default(false)
249
+ shareUrl String? @unique
222
250
  sharedAt DateTime?
223
- createdAt DateTime @default(now())
224
- updatedAt DateTime @updatedAt
251
+ createdAt DateTime @default(now())
252
+ updatedAt DateTime @updatedAt
225
253
  summary String?
226
- messagesSinceLastSummary Int? @default(0)
254
+ messagesSinceLastSummary Int? @default(0)
227
255
  // Relácie
228
256
  answers Answer[]
229
- user User @relation(fields: [userId], references: [id])
257
+ user User @relation(fields: [userId], references: [id])
230
258
  workflowLogs WorkflowLog[]
231
259
  canvasDocuments CanvasDocument[]
232
260
 
@@ -236,29 +264,29 @@ model Conversation {
236
264
 
237
265
  // Hlavný model - všetky správy, bez duplikátov
238
266
  model Answer {
239
- id String @id @default(cuid())
240
- conversationId String
241
- messageId String @unique
242
- role Role
243
- content String
244
- question String?
245
- answer String?
246
- evaluation String?
247
- isWelcome Boolean @default(false)
248
- processingTime Int?
249
- model String?
250
- userId String?
251
- createdAt DateTime @default(now())
252
- updatedAt DateTime @updatedAt
267
+ id String @id @default(cuid())
268
+ conversationId String
269
+ messageId String @unique
270
+ role Role
271
+ content String
272
+ question String?
273
+ answer String?
274
+ evaluation String?
275
+ isWelcome Boolean @default(false)
276
+ processingTime Int?
277
+ model String?
278
+ userId String?
279
+ createdAt DateTime @default(now())
280
+ updatedAt DateTime @updatedAt
253
281
  // Relácie
254
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
255
- user User? @relation(fields: [userId], references: [id])
282
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
283
+ user User? @relation(fields: [userId], references: [id])
256
284
  feedback Feedback?
257
285
  references Reference[]
258
286
  metrics AnswerMetrics?
259
287
  WorkflowLog WorkflowLog[]
260
288
  files MessageFile[]
261
- canvasDocumentId String? // No FK constraint - flexible document management
289
+ canvasDocumentId String? // No FK constraint - flexible document management
262
290
 
263
291
  @@index([conversationId])
264
292
  @@index([messageId])
@@ -268,13 +296,13 @@ model Answer {
268
296
  }
269
297
 
270
298
  model MessageFile {
271
- id String @id @default(cuid())
272
- answerId String
273
- fileName String
274
- fileType String
275
- base64Data String
276
- uploadedAt DateTime @default(now())
277
- answer Answer @relation(fields: [answerId], references: [id], onDelete: Cascade)
299
+ id String @id @default(cuid())
300
+ answerId String
301
+ fileName String
302
+ fileType String
303
+ base64Data String
304
+ uploadedAt DateTime @default(now())
305
+ answer Answer @relation(fields: [answerId], references: [id], onDelete: Cascade)
278
306
 
279
307
  @@index([answerId])
280
308
  @@index([fileType])
@@ -386,48 +414,250 @@ model PageView {
386
414
  }
387
415
 
388
416
  model Session {
389
- id String @id @default(cuid())
390
- sessionId String @unique
391
- userId String?
392
- startedAt DateTime @default(now())
393
- endedAt DateTime?
394
- duration Int?
417
+ id String @id @default(cuid())
418
+ sessionId String @unique
419
+ userId String?
420
+ startedAt DateTime @default(now())
421
+ endedAt DateTime?
422
+ duration Int?
423
+
424
+ // ============================================
425
+ // FIRST-TOUCH ATTRIBUTION
426
+ // ============================================
427
+ // Prvý UTM touchpoint v tejto session
428
+ // Automaticky naplnené pri prvom TouchPoint s UTM
429
+
430
+ firstUtmSource String?
431
+ firstUtmMedium String?
432
+ firstUtmCampaign String?
433
+ firstUtmContent String?
434
+ firstUtmTerm String?
435
+ firstTouchAt DateTime? // Timestamp prvého UTM touchpoint
436
+
437
+ // ============================================
438
+ // LAST-TOUCH ATTRIBUTION
439
+ // ============================================
440
+ // Posledný UTM touchpoint v tejto session
441
+ // Aktualizované pri každom novom TouchPoint s UTM
442
+
443
+ lastUtmSource String?
444
+ lastUtmMedium String?
445
+ lastUtmCampaign String?
446
+ lastUtmContent String?
447
+ lastUtmTerm String?
448
+ lastTouchAt DateTime? // Timestamp posledného UTM touchpoint
449
+
450
+ // ============================================
451
+ // RELATIONS
452
+ // ============================================
453
+
454
+ touchPoints TouchPoint[] // Všetky touchpointy v session
455
+ conversions Conversion[] // Všetky konverzie v session
456
+
457
+ // Existujúce relations
395
458
  pageViews PageView[]
396
459
  user User? @relation(fields: [userId], references: [id])
397
460
  workflowLogs WorkflowLog[]
398
461
 
462
+ // ============================================
463
+ // INDEXES
464
+ // ============================================
465
+
466
+ @@index([firstUtmCampaign])
467
+ @@index([lastUtmCampaign])
468
+ @@index([firstUtmSource, firstUtmMedium])
469
+ @@index([lastUtmSource, lastUtmMedium])
470
+ // Existujúce indexes
399
471
  @@index([userId])
400
472
  @@index([startedAt])
401
473
  }
402
474
 
475
+ // ============================================
476
+ // UTM TRACKING & ATTRIBUTION MODELS
477
+ // ============================================
478
+
479
+ // TouchPoint - každý click s UTM parametrami
480
+ // Umožňuje multi-touch attribution a customer journey analysis
481
+ model TouchPoint {
482
+ id String @id @default(cuid())
483
+ userId String? // Pre prihlásených používateľov
484
+ sessionId String // Session tracking (required)
485
+
486
+ // ============================================
487
+ // UTM PARAMETERS (všetky lowercase!)
488
+ // ============================================
489
+ // Best practice: Vždy lowercase, validované pred uložením
490
+
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"
496
+
497
+ // ============================================
498
+ // CONTEXT & METADATA
499
+ // ============================================
500
+
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)
504
+
505
+ // 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"
509
+
510
+ // Geolocation (anonymizované)
511
+ country String? // "SK", "CZ", "US"
512
+ city String? // "Bratislava", "Prague"
513
+
514
+ // Timestamp
515
+ timestamp DateTime @default(now())
516
+
517
+ // ============================================
518
+ // RELATIONS
519
+ // ============================================
520
+
521
+ session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
522
+ user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
523
+
524
+ // ============================================
525
+ // INDEXES (optimalizované pre analytics queries)
526
+ // ============================================
527
+
528
+ @@index([userId])
529
+ @@index([sessionId])
530
+ @@index([utmSource])
531
+ @@index([utmMedium])
532
+ @@index([utmCampaign])
533
+ @@index([utmSource, utmMedium])
534
+ @@index([utmCampaign, timestamp])
535
+ @@index([timestamp])
536
+ @@index([sessionId, timestamp])
537
+ @@index([userId, timestamp])
538
+ @@map("touch_points")
539
+ }
540
+
541
+ // Conversion - sledovanie konverzií s attribution
542
+ // Podporuje first-touch, last-touch aj multi-touch attribution
543
+ model Conversion {
544
+ id String @id @default(cuid())
545
+ userId String? // Kto konvertoval (nullable pre anonymous conversions)
546
+ sessionId String // V ktorej session
547
+
548
+ // ============================================
549
+ // CONVERSION METADATA
550
+ // ============================================
551
+
552
+ type ConversionType // Typ konverzie
553
+ value Decimal? // Revenue (pre e-commerce/subscriptions)
554
+ currency String? @default("EUR")
555
+
556
+ // Optional metadata
557
+ metadata Json? // Stripe subscription ID, product info, etc.
558
+
559
+ // ============================================
560
+ // FIRST-TOUCH ATTRIBUTION (40% credit)
561
+ // ============================================
562
+ // Prvý UTM touchpoint v session = awareness channel
563
+
564
+ firstUtmSource String? // Odkiaľ prišiel prvýkrát
565
+ firstUtmMedium String? // Akým médiom prvýkrát
566
+ firstUtmCampaign String? // Ktorá kampaň priniesla awareness
567
+ firstUtmContent String? // Ktorý konkrétny link/banner
568
+ firstUtmTerm String? // Ktoré keywords (pre paid search)
569
+ firstTouchAt DateTime? // Kedy bol prvý touchpoint
570
+
571
+ // ============================================
572
+ // LAST-TOUCH ATTRIBUTION (40% credit)
573
+ // ============================================
574
+ // Posledný UTM touchpoint pred konverziou = conversion channel
575
+
576
+ lastUtmSource String? // Odkiaľ prišiel naposledy
577
+ lastUtmMedium String? // Akým médiom naposledy
578
+ lastUtmCampaign String? // Ktorá kampaň priniesla konverziu
579
+ lastUtmContent String? // Ktorý konkrétny link/banner
580
+ lastUtmTerm String? // Ktoré keywords (pre paid search)
581
+ lastTouchAt DateTime? // Kedy bol posledný touchpoint
582
+
583
+ // ============================================
584
+ // MULTI-TOUCH ATTRIBUTION (20% credit)
585
+ // ============================================
586
+ // Všetky touchpointy medzi prvým a posledným
587
+
588
+ touchPointIds String[] // Array of TouchPoint IDs (pre detailnú analýzu)
589
+ touchPointCount Int? // Počet touchpoints v customer journey
590
+
591
+ // ============================================
592
+ // CALCULATED FIELDS
593
+ // ============================================
594
+
595
+ journeyDuration Int? // Čas od prvého touchpoint po konverziu (sekundy)
596
+
597
+ // Timestamp
598
+ timestamp DateTime @default(now())
599
+
600
+ // ============================================
601
+ // RELATIONS
602
+ // ============================================
603
+
604
+ session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
605
+ user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
606
+
607
+ // ============================================
608
+ // INDEXES
609
+ // ============================================
610
+
611
+ @@index([userId])
612
+ @@index([sessionId])
613
+ @@index([type])
614
+ @@index([timestamp])
615
+ // First-touch attribution indexes
616
+ @@index([firstUtmSource])
617
+ @@index([firstUtmMedium])
618
+ @@index([firstUtmCampaign])
619
+ @@index([firstUtmCampaign, type])
620
+ @@index([firstUtmSource, firstUtmMedium])
621
+ // Last-touch attribution indexes
622
+ @@index([lastUtmSource])
623
+ @@index([lastUtmMedium])
624
+ @@index([lastUtmCampaign])
625
+ @@index([lastUtmCampaign, type])
626
+ @@index([lastUtmSource, lastUtmMedium])
627
+ // Analytics indexes
628
+ @@index([type, timestamp])
629
+ @@index([value])
630
+ @@map("conversions")
631
+ }
632
+
403
633
  // ============================================
404
634
  // STRIPE INTEGRATION MODELS
405
635
  // ============================================
406
636
 
407
637
  // Stripe Customer - polymorphic relation (User OR Organization)
408
638
  model StripeCustomer {
409
- id String @id @default(cuid())
639
+ id String @id @default(cuid())
410
640
 
411
641
  // ✅ Polymorphic relation - buď User (B2C) alebo Organization (B2B)
412
- userId String? @unique // B2C: Individuálny používateľ
413
- organizationId String? @unique // B2B: Organizácia
642
+ userId String? @unique // B2C: Individuálny používateľ
643
+ organizationId String? @unique // B2B: Organizácia
414
644
 
415
- stripeCustomerId String @unique
416
- email String
417
- name String?
645
+ stripeCustomerId String @unique
646
+ email String
647
+ name String?
418
648
 
419
649
  // Billing details (Stripe Address & Tax IDs)
420
- billingAddress Json? // Stripe Address object
421
- taxIds Json? // Stripe Tax IDs array (VAT, etc.)
650
+ billingAddress Json? // Stripe Address object
651
+ taxIds Json? // Stripe Tax IDs array (VAT, etc.)
422
652
 
423
- createdAt DateTime @default(now())
424
- updatedAt DateTime @updatedAt
653
+ createdAt DateTime @default(now())
654
+ updatedAt DateTime @updatedAt
425
655
 
426
656
  // Relations
427
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
428
- organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
429
- subscriptions StripeSubscription[]
430
- payments StripePayment[]
657
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
658
+ organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
659
+ subscriptions StripeSubscription[]
660
+ payments StripePayment[]
431
661
 
432
662
  @@index([stripeCustomerId])
433
663
  @@index([userId])
@@ -437,37 +667,37 @@ model StripeCustomer {
437
667
 
438
668
  // Stripe Subscription - sledovanie predplatného (User alebo Organization)
439
669
  model StripeSubscription {
440
- id String @id @default(cuid())
670
+ id String @id @default(cuid())
441
671
 
442
672
  // ✅ FIX: customerId = FK na StripeCustomer.id (nie stripeCustomerId!)
443
- customerId String // FK na StripeCustomer.id
444
- stripeCustomerId String // Stripe ID (pre logging/redundancia)
445
- stripeSubscriptionId String @unique
673
+ customerId String // FK na StripeCustomer.id
674
+ stripeCustomerId String // Stripe ID (pre logging/redundancia)
675
+ stripeSubscriptionId String @unique
446
676
  stripePriceId String
447
677
  stripeProductId String
448
678
 
449
- status SubscriptionStatus
450
- tier SubscriptionTier @default(FREE)
451
- billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
679
+ status SubscriptionStatus
680
+ tier SubscriptionTier @default(FREE)
681
+ billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
452
682
 
453
- currentPeriodStart DateTime
454
- currentPeriodEnd DateTime
455
- cancelAtPeriodEnd Boolean @default(false)
456
- canceledAt DateTime?
683
+ currentPeriodStart DateTime
684
+ currentPeriodEnd DateTime
685
+ cancelAtPeriodEnd Boolean @default(false)
686
+ canceledAt DateTime?
457
687
 
458
- trialStart DateTime?
459
- trialEnd DateTime?
688
+ trialStart DateTime?
689
+ trialEnd DateTime?
460
690
 
461
691
  // Additional fields
462
- quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
463
- defaultPaymentMethodId String? // Stripe payment method ID
692
+ quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
693
+ defaultPaymentMethodId String? // Stripe payment method ID
464
694
 
465
- metadata Json? // Dodatočné Stripe metadata
466
- createdAt DateTime @default(now())
467
- updatedAt DateTime @updatedAt
695
+ metadata Json? // Dodatočné Stripe metadata
696
+ createdAt DateTime @default(now())
697
+ updatedAt DateTime @updatedAt
468
698
 
469
699
  // ✅ FIX: Relácia na customerId (interné ID), nie stripeCustomerId (Stripe ID)
470
- customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
700
+ customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
471
701
 
472
702
  @@index([stripeSubscriptionId])
473
703
  @@index([customerId])
@@ -480,14 +710,14 @@ model StripeSubscription {
480
710
 
481
711
  // Stripe Event - prevencia duplicitného spracovania webhookov (idempotencia)
482
712
  model StripeEvent {
483
- id String @id @default(cuid())
484
- eventId String @unique // Stripe event ID
485
- type String // Typ eventu (napr. 'checkout.session.completed')
486
- data Json // Úplné event data pre debugging
487
- processed Boolean @default(false)
713
+ id String @id @default(cuid())
714
+ eventId String @unique // Stripe event ID
715
+ type String // Typ eventu (napr. 'checkout.session.completed')
716
+ data Json // Úplné event data pre debugging
717
+ processed Boolean @default(false)
488
718
  processedAt DateTime?
489
- error String? // Uloženie chýb pri spracovaní
490
- createdAt DateTime @default(now())
719
+ error String? // Uloženie chýb pri spracovaní
720
+ createdAt DateTime @default(now())
491
721
 
492
722
  @@index([eventId])
493
723
  @@index([processed])
@@ -497,28 +727,28 @@ model StripeEvent {
497
727
 
498
728
  // Stripe Payment - sledovanie jednotlivých platieb (voliteľné, pre detailnú analytiku)
499
729
  model StripePayment {
500
- id String @id @default(cuid())
730
+ id String @id @default(cuid())
501
731
 
502
732
  // ✅ FIX: Pridaná relácia na StripeCustomer
503
- customerId String
504
- stripePaymentId String @unique
505
- stripeCustomerId String // Redundantné, ale OK pre logging
733
+ customerId String
734
+ stripePaymentId String @unique
735
+ stripeCustomerId String // Redundantné, ale OK pre logging
506
736
 
507
- amount Int // Suma v centoch
508
- currency String @default("eur")
509
- status String // succeeded, failed, pending
510
- paymentMethod String? // card, sepa_debit, atď.
511
- description String?
737
+ amount Int // Suma v centoch
738
+ currency String @default("eur")
739
+ status String // succeeded, failed, pending
740
+ paymentMethod String? // card, sepa_debit, atď.
741
+ description String?
512
742
 
513
743
  // Invoice info
514
- invoiceId String? // Stripe Invoice ID
515
- invoiceUrl String? // URL na faktúru
744
+ invoiceId String? // Stripe Invoice ID
745
+ invoiceUrl String? // URL na faktúru
516
746
 
517
- metadata Json?
518
- createdAt DateTime @default(now())
747
+ metadata Json?
748
+ createdAt DateTime @default(now())
519
749
 
520
750
  // ✅ Relácia na StripeCustomer
521
- customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
751
+ customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
522
752
 
523
753
  @@index([stripePaymentId])
524
754
  @@index([customerId])
@@ -559,35 +789,35 @@ enum ApprovalStatus {
559
789
 
560
790
  // Stripe: Status predplatného
561
791
  enum SubscriptionStatus {
562
- ACTIVE // Predplatné je aktívne
563
- CANCELED // Predplatné zrušené
564
- INCOMPLETE // Iniciálna platba zlyhala
792
+ ACTIVE // Predplatné je aktívne
793
+ CANCELED // Predplatné zrušené
794
+ INCOMPLETE // Iniciálna platba zlyhala
565
795
  INCOMPLETE_EXPIRED // Neúplné predplatné expirované
566
- PAST_DUE // Platba zlyhala, retry prebieha
567
- TRIALING // V skúšobnom období
568
- UNPAID // Platba zlyhala, žiadny retry
569
- PAUSED // Predplatné pozastavené (Stripe feature)
796
+ PAST_DUE // Platba zlyhala, retry prebieha
797
+ TRIALING // V skúšobnom období
798
+ UNPAID // Platba zlyhala, žiadny retry
799
+ PAUSED // Predplatné pozastavené (Stripe feature)
570
800
  }
571
801
 
572
802
  // Customer type: Individuálny vs SZČO
573
803
  enum CustomerType {
574
- INDIVIDUAL // Fyzická osoba (bez IČO)
575
- SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
804
+ INDIVIDUAL // Fyzická osoba (bez IČO)
805
+ SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
576
806
  }
577
807
 
578
808
  // Stripe: Tier predplatného
579
809
  enum SubscriptionTier {
580
- FREE // Free tier (10 messages/month)
581
- LAWYER // Lawyer tier (1000 messages/month, €29/month, 1 user)
582
- LAWYER_PRO // Lawyer Pro tier (3000 messages/month, €49/month, 1 user) - RECOMMENDED
583
- LAW_FIRM // Law Firm tier (10000 messages/month, €129/month, up to 5 users)
584
- ENTERPRISE // Enterprise tier (unlimited messages, unlimited users, custom pricing)
810
+ FREE // Free tier (10 messages/month)
811
+ LAWYER // Lawyer tier (1000 messages/month, €29/month, 1 user)
812
+ LAWYER_PRO // Lawyer Pro tier (3000 messages/month, €49/month, 1 user) - RECOMMENDED
813
+ LAW_FIRM // Law Firm tier (10000 messages/month, €129/month, up to 5 users)
814
+ ENTERPRISE // Enterprise tier (unlimited messages, unlimited users, custom pricing)
585
815
  }
586
816
 
587
817
  // Stripe: Billing interval
588
818
  enum BillingInterval {
589
- MONTHLY // Monthly billing
590
- YEARLY // Yearly billing (17% discount)
819
+ MONTHLY // Monthly billing
820
+ YEARLY // Yearly billing (17% discount)
591
821
  }
592
822
 
593
823
  // Canvas Document Status
@@ -597,6 +827,20 @@ enum DocumentStatus {
597
827
  ARCHIVED
598
828
  }
599
829
 
830
+ // Conversion types for UTM attribution tracking
831
+ enum ConversionType {
832
+ REGISTRATION // Nová registrácia
833
+ SUBSCRIPTION // Začiatok predplatného (FREE → PAID)
834
+ UPGRADE // Upgrade tieru (LAWYER → LAWYER_PRO)
835
+ RENEWAL // Obnovenie predplatného
836
+ TRIAL_START // Začiatok trial periodu
837
+ PURCHASE // Jednorazový nákup
838
+ LEAD // Vyplnenie kontaktného formulára
839
+ DEMO_REQUEST // Žiadosť o demo
840
+ DOWNLOAD // Stiahnutie resourceu
841
+ FORM_SUBMIT // Iné formuláre
842
+ }
843
+
600
844
  // ============================================
601
845
  // FOLDER SYSTEM ENUMS
602
846
  // ============================================
@@ -721,12 +965,12 @@ enum StepType {
721
965
 
722
966
  // Email verification tokens
723
967
  model VerificationToken {
724
- id String @id @default(cuid())
725
- userId String
726
- token String @unique
727
- expires DateTime
728
- createdAt DateTime @default(now())
729
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
968
+ id String @id @default(cuid())
969
+ userId String
970
+ token String @unique
971
+ expires DateTime
972
+ createdAt DateTime @default(now())
973
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
730
974
 
731
975
  @@index([token])
732
976
  @@index([userId])
@@ -735,12 +979,12 @@ model VerificationToken {
735
979
 
736
980
  // Password reset tokens
737
981
  model PasswordResetToken {
738
- id String @id @default(cuid())
739
- userId String
740
- token String @unique
741
- expires DateTime
742
- createdAt DateTime @default(now())
743
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
982
+ id String @id @default(cuid())
983
+ userId String
984
+ token String @unique
985
+ expires DateTime
986
+ createdAt DateTime @default(now())
987
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
744
988
 
745
989
  @@index([token])
746
990
  @@index([userId])
@@ -752,21 +996,21 @@ model PasswordResetToken {
752
996
  // ============================================
753
997
 
754
998
  model CanvasDocument {
755
- id String @id @default(cuid())
999
+ id String @id @default(cuid())
756
1000
  userId String
757
1001
  title String
758
- status DocumentStatus @default(DRAFT)
759
- createdAt DateTime @default(now())
760
- updatedAt DateTime @updatedAt
1002
+ status DocumentStatus @default(DRAFT)
1003
+ createdAt DateTime @default(now())
1004
+ updatedAt DateTime @updatedAt
761
1005
  originConversationId String?
762
- originAnswerId String? // No FK constraint - async save reference
763
- currentVersionId String? @unique
1006
+ originAnswerId String? // No FK constraint - async save reference
1007
+ currentVersionId String? @unique
764
1008
 
765
1009
  // Relations
766
- user User @relation(fields: [userId], references: [id], onDelete: Restrict)
767
- originConversation Conversation? @relation(fields: [originConversationId], references: [id], onDelete: SetNull)
768
- currentVersion CanvasDocumentVersion? @relation("CurrentVersion", fields: [currentVersionId], references: [id], onDelete: SetNull)
769
- versions CanvasDocumentVersion[] @relation("DocumentVersions")
1010
+ user User @relation(fields: [userId], references: [id], onDelete: Restrict)
1011
+ originConversation Conversation? @relation(fields: [originConversationId], references: [id], onDelete: SetNull)
1012
+ currentVersion CanvasDocumentVersion? @relation("CurrentVersion", fields: [currentVersionId], references: [id], onDelete: SetNull)
1013
+ versions CanvasDocumentVersion[] @relation("DocumentVersions")
770
1014
 
771
1015
  @@index([userId])
772
1016
  @@index([originConversationId])
@@ -775,12 +1019,12 @@ model CanvasDocument {
775
1019
  }
776
1020
 
777
1021
  model CanvasDocumentVersion {
778
- id String @id @default(cuid())
1022
+ id String @id @default(cuid())
779
1023
  documentId String
780
1024
  versionNumber Int
781
1025
  title String
782
1026
  markdownContent String
783
- createdAt DateTime @default(now())
1027
+ createdAt DateTime @default(now())
784
1028
  createdBy String
785
1029
  regenerationPrompt String?
786
1030
  parentVersionId String?
@@ -805,34 +1049,34 @@ model CanvasDocumentVersion {
805
1049
  // Main folder entity with hierarchy support via parentId
806
1050
  // Uses materialized path pattern for efficient hierarchy queries
807
1051
  model Folder {
808
- id String @id @default(cuid())
1052
+ id String @id @default(cuid())
809
1053
  name String
810
1054
  description String?
811
- color String? @default("#6366f1")
812
- icon String? @default("folder")
1055
+ color String? @default("#6366f1")
1056
+ icon String? @default("folder")
813
1057
 
814
1058
  // Ownership (always user-owned)
815
- ownerId String
1059
+ ownerId String
816
1060
 
817
1061
  // Hierarchy
818
1062
  parentId String?
819
- rootFolderId String? // NULL for root folders, points to top-level ancestor
820
- path String @default("/") // Materialized path: "/parentId/grandparentId/..."
821
- depth Int @default(0)
1063
+ rootFolderId String? // NULL for root folders, points to top-level ancestor
1064
+ path String @default("/") // Materialized path: "/parentId/grandparentId/..."
1065
+ depth Int @default(0)
822
1066
 
823
1067
  // Metadata
824
- sortOrder Int @default(0)
825
- isArchived Boolean @default(false)
826
- createdAt DateTime @default(now())
827
- updatedAt DateTime @updatedAt
1068
+ sortOrder Int @default(0)
1069
+ isArchived Boolean @default(false)
1070
+ createdAt DateTime @default(now())
1071
+ updatedAt DateTime @updatedAt
828
1072
 
829
1073
  // Relations
830
- owner User @relation("FolderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
831
- parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
832
- children Folder[] @relation("FolderHierarchy")
833
- items FolderItem[]
834
- activities FolderActivity[]
835
- shares FolderShare[]
1074
+ owner User @relation("FolderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
1075
+ parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
1076
+ children Folder[] @relation("FolderHierarchy")
1077
+ items FolderItem[]
1078
+ activities FolderActivity[]
1079
+ shares FolderShare[]
836
1080
 
837
1081
  @@index([ownerId])
838
1082
  @@index([parentId])
@@ -845,12 +1089,12 @@ model Folder {
845
1089
  // Links entities (conversations, documents, etc.) to folders
846
1090
  // Polymorphic: entityType + entityId point to any supported entity
847
1091
  model FolderItem {
848
- id String @id @default(cuid())
849
- folderId String
1092
+ id String @id @default(cuid())
1093
+ folderId String
850
1094
 
851
1095
  // Polymorphic reference
852
- entityType FolderItemType
853
- entityId String
1096
+ entityType FolderItemType
1097
+ entityId String
854
1098
 
855
1099
  // Optional metadata override
856
1100
  displayName String?
@@ -860,14 +1104,14 @@ model FolderItem {
860
1104
  rootFolderId String?
861
1105
 
862
1106
  // Tracking
863
- addedById String
864
- sortOrder Int @default(0)
865
- createdAt DateTime @default(now())
866
- updatedAt DateTime @updatedAt
1107
+ addedById String
1108
+ sortOrder Int @default(0)
1109
+ createdAt DateTime @default(now())
1110
+ updatedAt DateTime @updatedAt
867
1111
 
868
1112
  // Relations
869
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
870
- addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
1113
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1114
+ addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
871
1115
 
872
1116
  @@unique([folderId, entityType, entityId])
873
1117
  @@index([folderId])
@@ -879,25 +1123,25 @@ model FolderItem {
879
1123
  // Timeline entries for folders (system events + user messages)
880
1124
  // Opening ANY folder shows ALL activities from the entire folder tree
881
1125
  model FolderActivity {
882
- id String @id @default(cuid())
883
- folderId String
1126
+ id String @id @default(cuid())
1127
+ folderId String
884
1128
 
885
1129
  // Denormalized for cross-folder timeline queries
886
- rootFolderId String?
1130
+ rootFolderId String?
887
1131
 
888
1132
  // Activity details
889
1133
  activityType FolderActivityType
890
1134
  userId String
891
- content String? // For USER_MESSAGE, USER_NOTE types
892
- metadata Json? // For system event details
893
- relatedItemId String? // Optional reference to FolderItem
1135
+ content String? // For USER_MESSAGE, USER_NOTE types
1136
+ metadata Json? // For system event details
1137
+ relatedItemId String? // Optional reference to FolderItem
894
1138
 
895
1139
  // Timestamp
896
- createdAt DateTime @default(now())
1140
+ createdAt DateTime @default(now())
897
1141
 
898
1142
  // Relations
899
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
900
- user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
1143
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1144
+ user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
901
1145
 
902
1146
  @@index([folderId])
903
1147
  @@index([rootFolderId])
@@ -910,26 +1154,26 @@ model FolderActivity {
910
1154
  // Sharing permissions for folders
911
1155
  // Can share with individual user OR entire organization (not both)
912
1156
  model FolderShare {
913
- id String @id @default(cuid())
914
- folderId String
1157
+ id String @id @default(cuid())
1158
+ folderId String
915
1159
 
916
1160
  // Share target (either user OR organization, enforced by application logic)
917
1161
  userId String?
918
1162
  organizationId String?
919
1163
 
920
1164
  // Permission level
921
- permission FolderPermission @default(VIEW)
1165
+ permission FolderPermission @default(VIEW)
922
1166
 
923
1167
  // Sharing metadata
924
- sharedById String
925
- sharedAt DateTime @default(now())
926
- expiresAt DateTime?
1168
+ sharedById String
1169
+ sharedAt DateTime @default(now())
1170
+ expiresAt DateTime?
927
1171
 
928
1172
  // Relations
929
- folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
930
- user User? @relation("FolderShareUser", fields: [userId], references: [id], onDelete: Cascade)
931
- organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
932
- sharedBy User @relation("FolderShareCreator", fields: [sharedById], references: [id], onDelete: Restrict)
1173
+ folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
1174
+ user User? @relation("FolderShareUser", fields: [userId], references: [id], onDelete: Cascade)
1175
+ organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
1176
+ sharedBy User @relation("FolderShareCreator", fields: [sharedById], references: [id], onDelete: Restrict)
933
1177
 
934
1178
  // Partial unique indexes for nullable columns
935
1179
  @@unique([folderId, userId])
@@ -0,0 +1,198 @@
1
+ -- CreateEnum
2
+ CREATE TYPE "ConversionType" AS ENUM ('REGISTRATION', 'SUBSCRIPTION', 'UPGRADE', 'RENEWAL', 'TRIAL_START', 'PURCHASE', 'LEAD', 'DEMO_REQUEST', 'DOWNLOAD', 'FORM_SUBMIT');
3
+
4
+ -- AlterTable
5
+ ALTER TABLE "Session" ADD COLUMN "firstTouchAt" TIMESTAMP(3),
6
+ ADD COLUMN "firstUtmCampaign" TEXT,
7
+ ADD COLUMN "firstUtmContent" TEXT,
8
+ ADD COLUMN "firstUtmMedium" TEXT,
9
+ ADD COLUMN "firstUtmSource" TEXT,
10
+ ADD COLUMN "firstUtmTerm" TEXT,
11
+ ADD COLUMN "lastTouchAt" TIMESTAMP(3),
12
+ ADD COLUMN "lastUtmCampaign" TEXT,
13
+ ADD COLUMN "lastUtmContent" TEXT,
14
+ ADD COLUMN "lastUtmMedium" TEXT,
15
+ ADD COLUMN "lastUtmSource" TEXT,
16
+ ADD COLUMN "lastUtmTerm" TEXT;
17
+
18
+ -- AlterTable
19
+ ALTER TABLE "User" ADD COLUMN "firstUtmCampaign" TEXT,
20
+ ADD COLUMN "firstUtmContent" TEXT,
21
+ ADD COLUMN "firstUtmMedium" TEXT,
22
+ ADD COLUMN "firstUtmSource" TEXT,
23
+ ADD COLUMN "firstUtmTerm" TEXT,
24
+ ADD COLUMN "firstVisitAt" TIMESTAMP(3),
25
+ ADD COLUMN "registrationUtmCampaign" TEXT,
26
+ ADD COLUMN "registrationUtmContent" TEXT,
27
+ ADD COLUMN "registrationUtmMedium" TEXT,
28
+ ADD COLUMN "registrationUtmSource" TEXT,
29
+ ADD COLUMN "registrationUtmTerm" TEXT;
30
+
31
+ -- CreateTable
32
+ CREATE TABLE "touch_points" (
33
+ "id" TEXT NOT NULL,
34
+ "userId" TEXT,
35
+ "sessionId" TEXT NOT NULL,
36
+ "utmSource" TEXT,
37
+ "utmMedium" TEXT,
38
+ "utmCampaign" TEXT,
39
+ "utmContent" TEXT,
40
+ "utmTerm" TEXT,
41
+ "page" TEXT NOT NULL,
42
+ "path" TEXT NOT NULL,
43
+ "referrer" TEXT,
44
+ "deviceType" TEXT,
45
+ "browser" TEXT,
46
+ "os" TEXT,
47
+ "country" TEXT,
48
+ "city" TEXT,
49
+ "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
50
+
51
+ CONSTRAINT "touch_points_pkey" PRIMARY KEY ("id")
52
+ );
53
+
54
+ -- CreateTable
55
+ CREATE TABLE "conversions" (
56
+ "id" TEXT NOT NULL,
57
+ "userId" TEXT,
58
+ "sessionId" TEXT NOT NULL,
59
+ "type" "ConversionType" NOT NULL,
60
+ "value" DECIMAL(65,30),
61
+ "currency" TEXT DEFAULT 'EUR',
62
+ "metadata" JSONB,
63
+ "firstUtmSource" TEXT,
64
+ "firstUtmMedium" TEXT,
65
+ "firstUtmCampaign" TEXT,
66
+ "firstUtmContent" TEXT,
67
+ "firstUtmTerm" TEXT,
68
+ "firstTouchAt" TIMESTAMP(3),
69
+ "lastUtmSource" TEXT,
70
+ "lastUtmMedium" TEXT,
71
+ "lastUtmCampaign" TEXT,
72
+ "lastUtmContent" TEXT,
73
+ "lastUtmTerm" TEXT,
74
+ "lastTouchAt" TIMESTAMP(3),
75
+ "touchPointIds" TEXT[],
76
+ "touchPointCount" INTEGER,
77
+ "journeyDuration" INTEGER,
78
+ "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
79
+
80
+ CONSTRAINT "conversions_pkey" PRIMARY KEY ("id")
81
+ );
82
+
83
+ -- CreateIndex
84
+ CREATE INDEX "touch_points_userId_idx" ON "touch_points"("userId");
85
+
86
+ -- CreateIndex
87
+ CREATE INDEX "touch_points_sessionId_idx" ON "touch_points"("sessionId");
88
+
89
+ -- CreateIndex
90
+ CREATE INDEX "touch_points_utmSource_idx" ON "touch_points"("utmSource");
91
+
92
+ -- CreateIndex
93
+ CREATE INDEX "touch_points_utmMedium_idx" ON "touch_points"("utmMedium");
94
+
95
+ -- CreateIndex
96
+ CREATE INDEX "touch_points_utmCampaign_idx" ON "touch_points"("utmCampaign");
97
+
98
+ -- CreateIndex
99
+ CREATE INDEX "touch_points_utmSource_utmMedium_idx" ON "touch_points"("utmSource", "utmMedium");
100
+
101
+ -- CreateIndex
102
+ CREATE INDEX "touch_points_utmCampaign_timestamp_idx" ON "touch_points"("utmCampaign", "timestamp");
103
+
104
+ -- CreateIndex
105
+ CREATE INDEX "touch_points_timestamp_idx" ON "touch_points"("timestamp");
106
+
107
+ -- CreateIndex
108
+ CREATE INDEX "touch_points_sessionId_timestamp_idx" ON "touch_points"("sessionId", "timestamp");
109
+
110
+ -- CreateIndex
111
+ CREATE INDEX "touch_points_userId_timestamp_idx" ON "touch_points"("userId", "timestamp");
112
+
113
+ -- CreateIndex
114
+ CREATE INDEX "conversions_userId_idx" ON "conversions"("userId");
115
+
116
+ -- CreateIndex
117
+ CREATE INDEX "conversions_sessionId_idx" ON "conversions"("sessionId");
118
+
119
+ -- CreateIndex
120
+ CREATE INDEX "conversions_type_idx" ON "conversions"("type");
121
+
122
+ -- CreateIndex
123
+ CREATE INDEX "conversions_timestamp_idx" ON "conversions"("timestamp");
124
+
125
+ -- CreateIndex
126
+ CREATE INDEX "conversions_firstUtmSource_idx" ON "conversions"("firstUtmSource");
127
+
128
+ -- CreateIndex
129
+ CREATE INDEX "conversions_firstUtmMedium_idx" ON "conversions"("firstUtmMedium");
130
+
131
+ -- CreateIndex
132
+ CREATE INDEX "conversions_firstUtmCampaign_idx" ON "conversions"("firstUtmCampaign");
133
+
134
+ -- CreateIndex
135
+ CREATE INDEX "conversions_firstUtmCampaign_type_idx" ON "conversions"("firstUtmCampaign", "type");
136
+
137
+ -- CreateIndex
138
+ CREATE INDEX "conversions_firstUtmSource_firstUtmMedium_idx" ON "conversions"("firstUtmSource", "firstUtmMedium");
139
+
140
+ -- CreateIndex
141
+ CREATE INDEX "conversions_lastUtmSource_idx" ON "conversions"("lastUtmSource");
142
+
143
+ -- CreateIndex
144
+ CREATE INDEX "conversions_lastUtmMedium_idx" ON "conversions"("lastUtmMedium");
145
+
146
+ -- CreateIndex
147
+ CREATE INDEX "conversions_lastUtmCampaign_idx" ON "conversions"("lastUtmCampaign");
148
+
149
+ -- CreateIndex
150
+ CREATE INDEX "conversions_lastUtmCampaign_type_idx" ON "conversions"("lastUtmCampaign", "type");
151
+
152
+ -- CreateIndex
153
+ CREATE INDEX "conversions_lastUtmSource_lastUtmMedium_idx" ON "conversions"("lastUtmSource", "lastUtmMedium");
154
+
155
+ -- CreateIndex
156
+ CREATE INDEX "conversions_type_timestamp_idx" ON "conversions"("type", "timestamp");
157
+
158
+ -- CreateIndex
159
+ CREATE INDEX "conversions_value_idx" ON "conversions"("value");
160
+
161
+ -- CreateIndex
162
+ CREATE INDEX "Session_firstUtmCampaign_idx" ON "Session"("firstUtmCampaign");
163
+
164
+ -- CreateIndex
165
+ CREATE INDEX "Session_lastUtmCampaign_idx" ON "Session"("lastUtmCampaign");
166
+
167
+ -- CreateIndex
168
+ CREATE INDEX "Session_firstUtmSource_firstUtmMedium_idx" ON "Session"("firstUtmSource", "firstUtmMedium");
169
+
170
+ -- CreateIndex
171
+ CREATE INDEX "Session_lastUtmSource_lastUtmMedium_idx" ON "Session"("lastUtmSource", "lastUtmMedium");
172
+
173
+ -- CreateIndex
174
+ CREATE INDEX "User_firstUtmSource_idx" ON "User"("firstUtmSource");
175
+
176
+ -- CreateIndex
177
+ CREATE INDEX "User_firstUtmCampaign_idx" ON "User"("firstUtmCampaign");
178
+
179
+ -- CreateIndex
180
+ CREATE INDEX "User_registrationUtmSource_idx" ON "User"("registrationUtmSource");
181
+
182
+ -- CreateIndex
183
+ CREATE INDEX "User_registrationUtmCampaign_idx" ON "User"("registrationUtmCampaign");
184
+
185
+ -- CreateIndex
186
+ CREATE INDEX "User_firstVisitAt_idx" ON "User"("firstVisitAt");
187
+
188
+ -- AddForeignKey
189
+ ALTER TABLE "touch_points" ADD CONSTRAINT "touch_points_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "Session"("sessionId") ON DELETE CASCADE ON UPDATE CASCADE;
190
+
191
+ -- AddForeignKey
192
+ ALTER TABLE "touch_points" ADD CONSTRAINT "touch_points_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
193
+
194
+ -- AddForeignKey
195
+ ALTER TABLE "conversions" ADD CONSTRAINT "conversions_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "Session"("sessionId") ON DELETE CASCADE ON UPDATE CASCADE;
196
+
197
+ -- AddForeignKey
198
+ ALTER TABLE "conversions" ADD CONSTRAINT "conversions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -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");