@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
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
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
39
|
-
createdAt
|
|
40
|
-
agreedToTerms
|
|
41
|
-
practiceArea
|
|
42
|
-
lawFirm
|
|
43
|
-
yearsOfExperience
|
|
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
|
|
46
|
-
isRejected
|
|
47
|
-
approvedAt
|
|
48
|
-
rejectedAt
|
|
49
|
-
approvedBy
|
|
50
|
-
rejectedBy
|
|
51
|
-
rejectionReason
|
|
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
|
|
54
|
-
applicationText
|
|
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
|
|
57
|
-
messageCount
|
|
58
|
-
messageCountResetAt
|
|
59
|
-
adminGrantExpiresAt
|
|
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
|
|
62
|
-
companyName
|
|
63
|
-
companyNumber
|
|
64
|
-
vatNumber
|
|
65
|
-
taxNumber
|
|
66
|
-
companyStreet
|
|
67
|
-
companyCity
|
|
68
|
-
companyPostalCode
|
|
69
|
-
companyCountry
|
|
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
|
|
72
|
-
stripeCustomer
|
|
73
|
-
ownedOrganizations
|
|
74
|
-
organizationMembers
|
|
75
|
-
createdInvites
|
|
76
|
-
accounts
|
|
77
|
-
answers
|
|
78
|
-
conversations
|
|
79
|
-
feedbacks
|
|
80
|
-
pageViews
|
|
81
|
-
sessions
|
|
82
|
-
workflowLogs
|
|
83
|
-
verificationTokens
|
|
84
|
-
passwordResetTokens
|
|
85
|
-
canvasDocuments
|
|
86
|
-
canvasDocumentVersions
|
|
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
|
|
89
|
-
addedFolderItems
|
|
90
|
-
folderActivities
|
|
91
|
-
folderSharesReceived
|
|
92
|
-
createdFolderShares
|
|
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
|
|
122
|
-
name
|
|
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
|
|
126
|
-
vatNumber
|
|
127
|
-
taxNumber
|
|
128
|
-
legalForm
|
|
129
|
-
billingEmail
|
|
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
|
|
133
|
-
city
|
|
134
|
-
postalCode
|
|
135
|
-
country
|
|
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)
|
|
140
|
-
messageLimit Int @default(100)
|
|
141
|
-
messageCountResetAt DateTime?
|
|
142
|
-
adminGrantExpiresAt DateTime?
|
|
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
|
|
146
|
-
maxMembers
|
|
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
|
|
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
|
|
157
|
-
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
|
|
196
|
+
id String @id @default(cuid())
|
|
169
197
|
organizationId String
|
|
170
198
|
userId String
|
|
171
|
-
role MemberRole
|
|
172
|
-
joinedAt DateTime
|
|
199
|
+
role MemberRole @default(MEMBER)
|
|
200
|
+
joinedAt DateTime @default(now())
|
|
173
201
|
|
|
174
|
-
organization
|
|
175
|
-
user
|
|
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
|
|
213
|
+
id String @id @default(cuid())
|
|
186
214
|
organizationId String
|
|
187
|
-
token String
|
|
188
|
-
email String?
|
|
189
|
-
role MemberRole
|
|
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
|
|
193
|
-
currentUses Int
|
|
194
|
-
isActive Boolean
|
|
195
|
-
usedAt DateTime?
|
|
196
|
-
usedBy String?
|
|
197
|
-
createdAt DateTime
|
|
198
|
-
updatedAt DateTime
|
|
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
|
|
201
|
-
creator
|
|
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
|
|
211
|
-
ADMIN
|
|
212
|
-
MEMBER
|
|
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
|
|
245
|
+
id String @id @default(cuid())
|
|
218
246
|
name String
|
|
219
247
|
userId String
|
|
220
|
-
isShareable Boolean
|
|
221
|
-
shareUrl String?
|
|
248
|
+
isShareable Boolean @default(false)
|
|
249
|
+
shareUrl String? @unique
|
|
222
250
|
sharedAt DateTime?
|
|
223
|
-
createdAt DateTime
|
|
224
|
-
updatedAt DateTime
|
|
251
|
+
createdAt DateTime @default(now())
|
|
252
|
+
updatedAt DateTime @updatedAt
|
|
225
253
|
summary String?
|
|
226
|
-
messagesSinceLastSummary Int?
|
|
254
|
+
messagesSinceLastSummary Int? @default(0)
|
|
227
255
|
// Relácie
|
|
228
256
|
answers Answer[]
|
|
229
|
-
user User
|
|
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
|
|
240
|
-
conversationId
|
|
241
|
-
messageId
|
|
242
|
-
role
|
|
243
|
-
content
|
|
244
|
-
question
|
|
245
|
-
answer
|
|
246
|
-
evaluation
|
|
247
|
-
isWelcome
|
|
248
|
-
processingTime
|
|
249
|
-
model
|
|
250
|
-
userId
|
|
251
|
-
createdAt
|
|
252
|
-
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
|
|
255
|
-
user
|
|
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?
|
|
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
|
|
272
|
-
answerId
|
|
273
|
-
fileName
|
|
274
|
-
fileType
|
|
275
|
-
base64Data
|
|
276
|
-
uploadedAt
|
|
277
|
-
answer
|
|
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
|
|
390
|
-
sessionId
|
|
391
|
-
userId
|
|
392
|
-
startedAt
|
|
393
|
-
endedAt
|
|
394
|
-
duration
|
|
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
|
|
639
|
+
id String @id @default(cuid())
|
|
410
640
|
|
|
411
641
|
// ✅ Polymorphic relation - buď User (B2C) alebo Organization (B2B)
|
|
412
|
-
userId
|
|
413
|
-
organizationId
|
|
642
|
+
userId String? @unique // B2C: Individuálny používateľ
|
|
643
|
+
organizationId String? @unique // B2B: Organizácia
|
|
414
644
|
|
|
415
|
-
stripeCustomerId
|
|
416
|
-
email
|
|
417
|
-
name
|
|
645
|
+
stripeCustomerId String @unique
|
|
646
|
+
email String
|
|
647
|
+
name String?
|
|
418
648
|
|
|
419
649
|
// Billing details (Stripe Address & Tax IDs)
|
|
420
|
-
billingAddress
|
|
421
|
-
taxIds
|
|
650
|
+
billingAddress Json? // Stripe Address object
|
|
651
|
+
taxIds Json? // Stripe Tax IDs array (VAT, etc.)
|
|
422
652
|
|
|
423
|
-
createdAt
|
|
424
|
-
updatedAt
|
|
653
|
+
createdAt DateTime @default(now())
|
|
654
|
+
updatedAt DateTime @updatedAt
|
|
425
655
|
|
|
426
656
|
// Relations
|
|
427
|
-
user
|
|
428
|
-
organization
|
|
429
|
-
subscriptions
|
|
430
|
-
payments
|
|
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
|
|
670
|
+
id String @id @default(cuid())
|
|
441
671
|
|
|
442
672
|
// ✅ FIX: customerId = FK na StripeCustomer.id (nie stripeCustomerId!)
|
|
443
|
-
customerId String
|
|
444
|
-
stripeCustomerId String
|
|
445
|
-
stripeSubscriptionId String
|
|
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
|
|
450
|
-
tier
|
|
451
|
-
billingInterval
|
|
679
|
+
status SubscriptionStatus
|
|
680
|
+
tier SubscriptionTier @default(FREE)
|
|
681
|
+
billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
|
|
452
682
|
|
|
453
|
-
currentPeriodStart
|
|
454
|
-
currentPeriodEnd
|
|
455
|
-
cancelAtPeriodEnd
|
|
456
|
-
canceledAt
|
|
683
|
+
currentPeriodStart DateTime
|
|
684
|
+
currentPeriodEnd DateTime
|
|
685
|
+
cancelAtPeriodEnd Boolean @default(false)
|
|
686
|
+
canceledAt DateTime?
|
|
457
687
|
|
|
458
|
-
trialStart
|
|
459
|
-
trialEnd
|
|
688
|
+
trialStart DateTime?
|
|
689
|
+
trialEnd DateTime?
|
|
460
690
|
|
|
461
691
|
// Additional fields
|
|
462
|
-
quantity
|
|
463
|
-
defaultPaymentMethodId String?
|
|
692
|
+
quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
|
|
693
|
+
defaultPaymentMethodId String? // Stripe payment method ID
|
|
464
694
|
|
|
465
|
-
metadata
|
|
466
|
-
createdAt
|
|
467
|
-
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
|
|
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
|
|
484
|
-
eventId String
|
|
485
|
-
type String
|
|
486
|
-
data Json
|
|
487
|
-
processed Boolean
|
|
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?
|
|
490
|
-
createdAt DateTime
|
|
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
|
|
730
|
+
id String @id @default(cuid())
|
|
501
731
|
|
|
502
732
|
// ✅ FIX: Pridaná relácia na StripeCustomer
|
|
503
|
-
customerId
|
|
504
|
-
stripePaymentId
|
|
505
|
-
stripeCustomerId
|
|
733
|
+
customerId String
|
|
734
|
+
stripePaymentId String @unique
|
|
735
|
+
stripeCustomerId String // Redundantné, ale OK pre logging
|
|
506
736
|
|
|
507
|
-
amount
|
|
508
|
-
currency
|
|
509
|
-
status
|
|
510
|
-
paymentMethod
|
|
511
|
-
description
|
|
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
|
|
515
|
-
invoiceUrl
|
|
744
|
+
invoiceId String? // Stripe Invoice ID
|
|
745
|
+
invoiceUrl String? // URL na faktúru
|
|
516
746
|
|
|
517
|
-
metadata
|
|
518
|
-
createdAt
|
|
747
|
+
metadata Json?
|
|
748
|
+
createdAt DateTime @default(now())
|
|
519
749
|
|
|
520
750
|
// ✅ Relácia na StripeCustomer
|
|
521
|
-
customer
|
|
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
|
|
563
|
-
CANCELED
|
|
564
|
-
INCOMPLETE
|
|
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
|
|
567
|
-
TRIALING
|
|
568
|
-
UNPAID
|
|
569
|
-
PAUSED
|
|
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
|
|
575
|
-
SELF_EMPLOYED
|
|
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
|
|
581
|
-
LAWYER
|
|
582
|
-
LAWYER_PRO
|
|
583
|
-
LAW_FIRM
|
|
584
|
-
ENTERPRISE
|
|
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
|
|
590
|
-
YEARLY
|
|
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
|
|
725
|
-
userId
|
|
726
|
-
token
|
|
727
|
-
expires
|
|
728
|
-
createdAt
|
|
729
|
-
user
|
|
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
|
|
739
|
-
userId
|
|
740
|
-
token
|
|
741
|
-
expires
|
|
742
|
-
createdAt
|
|
743
|
-
user
|
|
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
|
|
999
|
+
id String @id @default(cuid())
|
|
756
1000
|
userId String
|
|
757
1001
|
title String
|
|
758
|
-
status DocumentStatus
|
|
759
|
-
createdAt DateTime
|
|
760
|
-
updatedAt DateTime
|
|
1002
|
+
status DocumentStatus @default(DRAFT)
|
|
1003
|
+
createdAt DateTime @default(now())
|
|
1004
|
+
updatedAt DateTime @updatedAt
|
|
761
1005
|
originConversationId String?
|
|
762
|
-
originAnswerId String?
|
|
763
|
-
currentVersionId String?
|
|
1006
|
+
originAnswerId String? // No FK constraint - async save reference
|
|
1007
|
+
currentVersionId String? @unique
|
|
764
1008
|
|
|
765
1009
|
// Relations
|
|
766
|
-
user
|
|
767
|
-
originConversation
|
|
768
|
-
currentVersion
|
|
769
|
-
versions
|
|
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
|
|
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
|
|
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
|
|
1052
|
+
id String @id @default(cuid())
|
|
809
1053
|
name String
|
|
810
1054
|
description String?
|
|
811
|
-
color String?
|
|
812
|
-
icon String?
|
|
1055
|
+
color String? @default("#6366f1")
|
|
1056
|
+
icon String? @default("folder")
|
|
813
1057
|
|
|
814
1058
|
// Ownership (always user-owned)
|
|
815
|
-
ownerId
|
|
1059
|
+
ownerId String
|
|
816
1060
|
|
|
817
1061
|
// Hierarchy
|
|
818
1062
|
parentId String?
|
|
819
|
-
rootFolderId String?
|
|
820
|
-
path String
|
|
821
|
-
depth Int
|
|
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
|
|
825
|
-
isArchived
|
|
826
|
-
createdAt
|
|
827
|
-
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
|
|
831
|
-
parent
|
|
832
|
-
children
|
|
833
|
-
items
|
|
834
|
-
activities
|
|
835
|
-
shares
|
|
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
|
|
849
|
-
folderId
|
|
1092
|
+
id String @id @default(cuid())
|
|
1093
|
+
folderId String
|
|
850
1094
|
|
|
851
1095
|
// Polymorphic reference
|
|
852
|
-
entityType
|
|
853
|
-
entityId
|
|
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
|
|
864
|
-
sortOrder
|
|
865
|
-
createdAt
|
|
866
|
-
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
|
|
870
|
-
addedBy
|
|
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
|
|
883
|
-
folderId
|
|
1126
|
+
id String @id @default(cuid())
|
|
1127
|
+
folderId String
|
|
884
1128
|
|
|
885
1129
|
// Denormalized for cross-folder timeline queries
|
|
886
|
-
rootFolderId
|
|
1130
|
+
rootFolderId String?
|
|
887
1131
|
|
|
888
1132
|
// Activity details
|
|
889
1133
|
activityType FolderActivityType
|
|
890
1134
|
userId String
|
|
891
|
-
content String?
|
|
892
|
-
metadata Json?
|
|
893
|
-
relatedItemId String?
|
|
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
|
|
1140
|
+
createdAt DateTime @default(now())
|
|
897
1141
|
|
|
898
1142
|
// Relations
|
|
899
|
-
folder
|
|
900
|
-
user
|
|
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
|
|
914
|
-
folderId
|
|
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
|
|
1165
|
+
permission FolderPermission @default(VIEW)
|
|
922
1166
|
|
|
923
1167
|
// Sharing metadata
|
|
924
|
-
sharedById
|
|
925
|
-
sharedAt
|
|
926
|
-
expiresAt
|
|
1168
|
+
sharedById String
|
|
1169
|
+
sharedAt DateTime @default(now())
|
|
1170
|
+
expiresAt DateTime?
|
|
927
1171
|
|
|
928
1172
|
// Relations
|
|
929
|
-
folder
|
|
930
|
-
user
|
|
931
|
-
organization
|
|
932
|
-
sharedBy
|
|
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");
|