@danielcok17/prisma-db 1.11.0 → 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,91 +23,96 @@ 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)
|
|
70
72
|
// ✨ UTM TRACKING & ATTRIBUTION FIELDS
|
|
71
73
|
// First-visit attribution (celý lifecycle)
|
|
72
|
-
firstUtmSource
|
|
73
|
-
firstUtmMedium
|
|
74
|
-
firstUtmCampaign
|
|
75
|
-
firstUtmContent
|
|
76
|
-
firstUtmTerm
|
|
77
|
-
firstVisitAt
|
|
74
|
+
firstUtmSource String? // Prvý UTM source
|
|
75
|
+
firstUtmMedium String? // Prvý UTM medium
|
|
76
|
+
firstUtmCampaign String? // Prvá kampaň
|
|
77
|
+
firstUtmContent String? // Prvý content/variant
|
|
78
|
+
firstUtmTerm String? // Prvé keywords
|
|
79
|
+
firstVisitAt DateTime? // Kedy prvýkrát navštívil web
|
|
78
80
|
// Registration attribution
|
|
79
|
-
registrationUtmSource String?
|
|
80
|
-
registrationUtmMedium String?
|
|
81
|
-
registrationUtmCampaign String?
|
|
82
|
-
registrationUtmContent String?
|
|
83
|
-
registrationUtmTerm String?
|
|
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
|
|
84
86
|
// Relations
|
|
85
|
-
approvalRequest
|
|
86
|
-
stripeCustomer
|
|
87
|
-
ownedOrganizations
|
|
88
|
-
organizationMembers
|
|
89
|
-
createdInvites
|
|
90
|
-
accounts
|
|
91
|
-
answers
|
|
92
|
-
conversations
|
|
93
|
-
feedbacks
|
|
94
|
-
pageViews
|
|
95
|
-
sessions
|
|
96
|
-
workflowLogs
|
|
97
|
-
verificationTokens
|
|
98
|
-
passwordResetTokens
|
|
99
|
-
canvasDocuments
|
|
100
|
-
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[]
|
|
101
103
|
// Folder system relations
|
|
102
|
-
ownedFolders
|
|
103
|
-
addedFolderItems
|
|
104
|
-
folderActivities
|
|
105
|
-
folderSharesReceived
|
|
106
|
-
createdFolderShares
|
|
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")
|
|
107
109
|
// UTM tracking relations
|
|
108
|
-
touchPoints
|
|
109
|
-
conversions
|
|
110
|
+
touchPoints TouchPoint[]
|
|
111
|
+
conversions Conversion[]
|
|
110
112
|
|
|
113
|
+
// Multi-brand: compound unique allows same email across brands
|
|
114
|
+
@@unique([email, brand])
|
|
115
|
+
@@index([brand])
|
|
111
116
|
@@index([customerType])
|
|
112
117
|
@@index([companyNumber])
|
|
113
118
|
@@index([email, customerType])
|
|
@@ -141,43 +146,43 @@ model UserApprovalRequest {
|
|
|
141
146
|
|
|
142
147
|
// Organization = Právna kancelária / Firma
|
|
143
148
|
model Organization {
|
|
144
|
-
id
|
|
145
|
-
name
|
|
149
|
+
id String @id @default(cuid())
|
|
150
|
+
name String // "Advokátska kancelária XYZ s.r.o."
|
|
146
151
|
|
|
147
152
|
// Billing & Legal Information
|
|
148
|
-
companyNumber
|
|
149
|
-
vatNumber
|
|
150
|
-
taxNumber
|
|
151
|
-
legalForm
|
|
152
|
-
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
|
|
153
158
|
|
|
154
159
|
// Address
|
|
155
|
-
street
|
|
156
|
-
city
|
|
157
|
-
postalCode
|
|
158
|
-
country
|
|
160
|
+
street String?
|
|
161
|
+
city String?
|
|
162
|
+
postalCode String?
|
|
163
|
+
country String @default("SK")
|
|
159
164
|
|
|
160
165
|
// Subscription (B2B)
|
|
161
166
|
subscriptionTier SubscriptionTier @default(FREE) // Tier organizácie (cache)
|
|
162
|
-
messageCount Int @default(0)
|
|
163
|
-
messageLimit Int @default(100)
|
|
164
|
-
messageCountResetAt DateTime?
|
|
165
|
-
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
|
|
166
171
|
|
|
167
172
|
// Settings
|
|
168
|
-
isActive
|
|
169
|
-
maxMembers
|
|
173
|
+
isActive Boolean @default(true)
|
|
174
|
+
maxMembers Int @default(5) // Max počet členov podľa tieru
|
|
170
175
|
|
|
171
176
|
// Relations
|
|
172
|
-
owner User
|
|
177
|
+
owner User @relation("OrganizationOwner", fields: [ownerId], references: [id])
|
|
173
178
|
ownerId String
|
|
174
179
|
members OrganizationMember[]
|
|
175
180
|
invites OrganizationInvite[]
|
|
176
181
|
stripeCustomer StripeCustomer?
|
|
177
182
|
folderShares FolderShare[]
|
|
178
183
|
|
|
179
|
-
createdAt
|
|
180
|
-
updatedAt
|
|
184
|
+
createdAt DateTime @default(now())
|
|
185
|
+
updatedAt DateTime @updatedAt
|
|
181
186
|
|
|
182
187
|
@@index([ownerId])
|
|
183
188
|
@@index([vatNumber])
|
|
@@ -188,14 +193,14 @@ model Organization {
|
|
|
188
193
|
|
|
189
194
|
// Many-to-Many: User ↔ Organization
|
|
190
195
|
model OrganizationMember {
|
|
191
|
-
id String
|
|
196
|
+
id String @id @default(cuid())
|
|
192
197
|
organizationId String
|
|
193
198
|
userId String
|
|
194
|
-
role MemberRole
|
|
195
|
-
joinedAt DateTime
|
|
199
|
+
role MemberRole @default(MEMBER)
|
|
200
|
+
joinedAt DateTime @default(now())
|
|
196
201
|
|
|
197
|
-
organization
|
|
198
|
-
user
|
|
202
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
203
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
199
204
|
|
|
200
205
|
@@unique([organizationId, userId])
|
|
201
206
|
@@index([organizationId])
|
|
@@ -205,23 +210,23 @@ model OrganizationMember {
|
|
|
205
210
|
|
|
206
211
|
// Organization Invite System
|
|
207
212
|
model OrganizationInvite {
|
|
208
|
-
id String
|
|
213
|
+
id String @id @default(cuid())
|
|
209
214
|
organizationId String
|
|
210
|
-
token String
|
|
211
|
-
email String?
|
|
212
|
-
role MemberRole
|
|
215
|
+
token String @unique
|
|
216
|
+
email String? // Optional: specific email invite
|
|
217
|
+
role MemberRole @default(MEMBER)
|
|
213
218
|
createdBy String
|
|
214
219
|
expiresAt DateTime
|
|
215
|
-
maxUses Int
|
|
216
|
-
currentUses Int
|
|
217
|
-
isActive Boolean
|
|
218
|
-
usedAt DateTime?
|
|
219
|
-
usedBy String?
|
|
220
|
-
createdAt DateTime
|
|
221
|
-
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
|
|
222
227
|
|
|
223
|
-
organization
|
|
224
|
-
creator
|
|
228
|
+
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
229
|
+
creator User @relation("InviteCreator", fields: [createdBy], references: [id])
|
|
225
230
|
|
|
226
231
|
@@index([token])
|
|
227
232
|
@@index([organizationId])
|
|
@@ -230,26 +235,26 @@ model OrganizationInvite {
|
|
|
230
235
|
}
|
|
231
236
|
|
|
232
237
|
enum MemberRole {
|
|
233
|
-
OWNER
|
|
234
|
-
ADMIN
|
|
235
|
-
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
|
|
236
241
|
}
|
|
237
242
|
|
|
238
243
|
// ZJEDNODUŠENÝ Conversation - len metadata, bez duplikátov
|
|
239
244
|
model Conversation {
|
|
240
|
-
id String
|
|
245
|
+
id String @id @default(cuid())
|
|
241
246
|
name String
|
|
242
247
|
userId String
|
|
243
|
-
isShareable Boolean
|
|
244
|
-
shareUrl String?
|
|
248
|
+
isShareable Boolean @default(false)
|
|
249
|
+
shareUrl String? @unique
|
|
245
250
|
sharedAt DateTime?
|
|
246
|
-
createdAt DateTime
|
|
247
|
-
updatedAt DateTime
|
|
251
|
+
createdAt DateTime @default(now())
|
|
252
|
+
updatedAt DateTime @updatedAt
|
|
248
253
|
summary String?
|
|
249
|
-
messagesSinceLastSummary Int?
|
|
254
|
+
messagesSinceLastSummary Int? @default(0)
|
|
250
255
|
// Relácie
|
|
251
256
|
answers Answer[]
|
|
252
|
-
user User
|
|
257
|
+
user User @relation(fields: [userId], references: [id])
|
|
253
258
|
workflowLogs WorkflowLog[]
|
|
254
259
|
canvasDocuments CanvasDocument[]
|
|
255
260
|
|
|
@@ -259,29 +264,29 @@ model Conversation {
|
|
|
259
264
|
|
|
260
265
|
// Hlavný model - všetky správy, bez duplikátov
|
|
261
266
|
model Answer {
|
|
262
|
-
id
|
|
263
|
-
conversationId
|
|
264
|
-
messageId
|
|
265
|
-
role
|
|
266
|
-
content
|
|
267
|
-
question
|
|
268
|
-
answer
|
|
269
|
-
evaluation
|
|
270
|
-
isWelcome
|
|
271
|
-
processingTime
|
|
272
|
-
model
|
|
273
|
-
userId
|
|
274
|
-
createdAt
|
|
275
|
-
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
|
|
276
281
|
// Relácie
|
|
277
|
-
conversation
|
|
278
|
-
user
|
|
282
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
283
|
+
user User? @relation(fields: [userId], references: [id])
|
|
279
284
|
feedback Feedback?
|
|
280
285
|
references Reference[]
|
|
281
286
|
metrics AnswerMetrics?
|
|
282
287
|
WorkflowLog WorkflowLog[]
|
|
283
288
|
files MessageFile[]
|
|
284
|
-
canvasDocumentId String?
|
|
289
|
+
canvasDocumentId String? // No FK constraint - flexible document management
|
|
285
290
|
|
|
286
291
|
@@index([conversationId])
|
|
287
292
|
@@index([messageId])
|
|
@@ -291,13 +296,13 @@ model Answer {
|
|
|
291
296
|
}
|
|
292
297
|
|
|
293
298
|
model MessageFile {
|
|
294
|
-
id
|
|
295
|
-
answerId
|
|
296
|
-
fileName
|
|
297
|
-
fileType
|
|
298
|
-
base64Data
|
|
299
|
-
uploadedAt
|
|
300
|
-
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)
|
|
301
306
|
|
|
302
307
|
@@index([answerId])
|
|
303
308
|
@@index([fileType])
|
|
@@ -409,12 +414,12 @@ model PageView {
|
|
|
409
414
|
}
|
|
410
415
|
|
|
411
416
|
model Session {
|
|
412
|
-
id
|
|
413
|
-
sessionId
|
|
414
|
-
userId
|
|
415
|
-
startedAt
|
|
416
|
-
endedAt
|
|
417
|
-
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?
|
|
418
423
|
|
|
419
424
|
// ============================================
|
|
420
425
|
// FIRST-TOUCH ATTRIBUTION
|
|
@@ -427,7 +432,7 @@ model Session {
|
|
|
427
432
|
firstUtmCampaign String?
|
|
428
433
|
firstUtmContent String?
|
|
429
434
|
firstUtmTerm String?
|
|
430
|
-
firstTouchAt DateTime?
|
|
435
|
+
firstTouchAt DateTime? // Timestamp prvého UTM touchpoint
|
|
431
436
|
|
|
432
437
|
// ============================================
|
|
433
438
|
// LAST-TOUCH ATTRIBUTION
|
|
@@ -440,14 +445,14 @@ model Session {
|
|
|
440
445
|
lastUtmCampaign String?
|
|
441
446
|
lastUtmContent String?
|
|
442
447
|
lastUtmTerm String?
|
|
443
|
-
lastTouchAt DateTime?
|
|
448
|
+
lastTouchAt DateTime? // Timestamp posledného UTM touchpoint
|
|
444
449
|
|
|
445
450
|
// ============================================
|
|
446
451
|
// RELATIONS
|
|
447
452
|
// ============================================
|
|
448
453
|
|
|
449
|
-
touchPoints
|
|
450
|
-
conversions
|
|
454
|
+
touchPoints TouchPoint[] // Všetky touchpointy v session
|
|
455
|
+
conversions Conversion[] // Všetky konverzie v session
|
|
451
456
|
|
|
452
457
|
// Existujúce relations
|
|
453
458
|
pageViews PageView[]
|
|
@@ -462,7 +467,6 @@ model Session {
|
|
|
462
467
|
@@index([lastUtmCampaign])
|
|
463
468
|
@@index([firstUtmSource, firstUtmMedium])
|
|
464
469
|
@@index([lastUtmSource, lastUtmMedium])
|
|
465
|
-
|
|
466
470
|
// Existujúce indexes
|
|
467
471
|
@@index([userId])
|
|
468
472
|
@@index([startedAt])
|
|
@@ -475,47 +479,47 @@ model Session {
|
|
|
475
479
|
// TouchPoint - každý click s UTM parametrami
|
|
476
480
|
// Umožňuje multi-touch attribution a customer journey analysis
|
|
477
481
|
model TouchPoint {
|
|
478
|
-
id String
|
|
479
|
-
userId String?
|
|
480
|
-
sessionId String
|
|
482
|
+
id String @id @default(cuid())
|
|
483
|
+
userId String? // Pre prihlásených používateľov
|
|
484
|
+
sessionId String // Session tracking (required)
|
|
481
485
|
|
|
482
486
|
// ============================================
|
|
483
487
|
// UTM PARAMETERS (všetky lowercase!)
|
|
484
488
|
// ============================================
|
|
485
489
|
// Best practice: Vždy lowercase, validované pred uložením
|
|
486
490
|
|
|
487
|
-
utmSource String?
|
|
488
|
-
utmMedium String?
|
|
489
|
-
utmCampaign String?
|
|
490
|
-
utmContent String?
|
|
491
|
-
utmTerm String?
|
|
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"
|
|
492
496
|
|
|
493
497
|
// ============================================
|
|
494
498
|
// CONTEXT & METADATA
|
|
495
499
|
// ============================================
|
|
496
500
|
|
|
497
|
-
page
|
|
498
|
-
path
|
|
499
|
-
referrer
|
|
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)
|
|
500
504
|
|
|
501
505
|
// Device & Browser info (denormalizované pre rýchle queries)
|
|
502
|
-
deviceType String?
|
|
503
|
-
browser String?
|
|
504
|
-
os String?
|
|
506
|
+
deviceType String? // "mobile", "tablet", "desktop"
|
|
507
|
+
browser String? // "Chrome", "Firefox", "Safari"
|
|
508
|
+
os String? // "Windows", "macOS", "iOS", "Android"
|
|
505
509
|
|
|
506
510
|
// Geolocation (anonymizované)
|
|
507
|
-
country
|
|
508
|
-
city
|
|
511
|
+
country String? // "SK", "CZ", "US"
|
|
512
|
+
city String? // "Bratislava", "Prague"
|
|
509
513
|
|
|
510
514
|
// Timestamp
|
|
511
|
-
timestamp
|
|
515
|
+
timestamp DateTime @default(now())
|
|
512
516
|
|
|
513
517
|
// ============================================
|
|
514
518
|
// RELATIONS
|
|
515
519
|
// ============================================
|
|
516
520
|
|
|
517
|
-
session
|
|
518
|
-
user
|
|
521
|
+
session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
|
|
522
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
519
523
|
|
|
520
524
|
// ============================================
|
|
521
525
|
// INDEXES (optimalizované pre analytics queries)
|
|
@@ -531,38 +535,37 @@ model TouchPoint {
|
|
|
531
535
|
@@index([timestamp])
|
|
532
536
|
@@index([sessionId, timestamp])
|
|
533
537
|
@@index([userId, timestamp])
|
|
534
|
-
|
|
535
538
|
@@map("touch_points")
|
|
536
539
|
}
|
|
537
540
|
|
|
538
541
|
// Conversion - sledovanie konverzií s attribution
|
|
539
542
|
// Podporuje first-touch, last-touch aj multi-touch attribution
|
|
540
543
|
model Conversion {
|
|
541
|
-
id
|
|
542
|
-
userId
|
|
543
|
-
sessionId
|
|
544
|
+
id String @id @default(cuid())
|
|
545
|
+
userId String? // Kto konvertoval (nullable pre anonymous conversions)
|
|
546
|
+
sessionId String // V ktorej session
|
|
544
547
|
|
|
545
548
|
// ============================================
|
|
546
549
|
// CONVERSION METADATA
|
|
547
550
|
// ============================================
|
|
548
551
|
|
|
549
|
-
type
|
|
550
|
-
value
|
|
551
|
-
currency
|
|
552
|
+
type ConversionType // Typ konverzie
|
|
553
|
+
value Decimal? // Revenue (pre e-commerce/subscriptions)
|
|
554
|
+
currency String? @default("EUR")
|
|
552
555
|
|
|
553
556
|
// Optional metadata
|
|
554
|
-
metadata
|
|
557
|
+
metadata Json? // Stripe subscription ID, product info, etc.
|
|
555
558
|
|
|
556
559
|
// ============================================
|
|
557
560
|
// FIRST-TOUCH ATTRIBUTION (40% credit)
|
|
558
561
|
// ============================================
|
|
559
562
|
// Prvý UTM touchpoint v session = awareness channel
|
|
560
563
|
|
|
561
|
-
firstUtmSource String?
|
|
562
|
-
firstUtmMedium String?
|
|
563
|
-
firstUtmCampaign String?
|
|
564
|
-
firstUtmContent String?
|
|
565
|
-
firstUtmTerm String?
|
|
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)
|
|
566
569
|
firstTouchAt DateTime? // Kedy bol prvý touchpoint
|
|
567
570
|
|
|
568
571
|
// ============================================
|
|
@@ -570,11 +573,11 @@ model Conversion {
|
|
|
570
573
|
// ============================================
|
|
571
574
|
// Posledný UTM touchpoint pred konverziou = conversion channel
|
|
572
575
|
|
|
573
|
-
lastUtmSource String?
|
|
574
|
-
lastUtmMedium String?
|
|
575
|
-
lastUtmCampaign String?
|
|
576
|
-
lastUtmContent String?
|
|
577
|
-
lastUtmTerm String?
|
|
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)
|
|
578
581
|
lastTouchAt DateTime? // Kedy bol posledný touchpoint
|
|
579
582
|
|
|
580
583
|
// ============================================
|
|
@@ -582,24 +585,24 @@ model Conversion {
|
|
|
582
585
|
// ============================================
|
|
583
586
|
// Všetky touchpointy medzi prvým a posledným
|
|
584
587
|
|
|
585
|
-
touchPointIds
|
|
586
|
-
touchPointCount
|
|
588
|
+
touchPointIds String[] // Array of TouchPoint IDs (pre detailnú analýzu)
|
|
589
|
+
touchPointCount Int? // Počet touchpoints v customer journey
|
|
587
590
|
|
|
588
591
|
// ============================================
|
|
589
592
|
// CALCULATED FIELDS
|
|
590
593
|
// ============================================
|
|
591
594
|
|
|
592
|
-
journeyDuration
|
|
595
|
+
journeyDuration Int? // Čas od prvého touchpoint po konverziu (sekundy)
|
|
593
596
|
|
|
594
597
|
// Timestamp
|
|
595
|
-
timestamp
|
|
598
|
+
timestamp DateTime @default(now())
|
|
596
599
|
|
|
597
600
|
// ============================================
|
|
598
601
|
// RELATIONS
|
|
599
602
|
// ============================================
|
|
600
603
|
|
|
601
|
-
session
|
|
602
|
-
user
|
|
604
|
+
session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
|
|
605
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
603
606
|
|
|
604
607
|
// ============================================
|
|
605
608
|
// INDEXES
|
|
@@ -609,25 +612,21 @@ model Conversion {
|
|
|
609
612
|
@@index([sessionId])
|
|
610
613
|
@@index([type])
|
|
611
614
|
@@index([timestamp])
|
|
612
|
-
|
|
613
615
|
// First-touch attribution indexes
|
|
614
616
|
@@index([firstUtmSource])
|
|
615
617
|
@@index([firstUtmMedium])
|
|
616
618
|
@@index([firstUtmCampaign])
|
|
617
619
|
@@index([firstUtmCampaign, type])
|
|
618
620
|
@@index([firstUtmSource, firstUtmMedium])
|
|
619
|
-
|
|
620
621
|
// Last-touch attribution indexes
|
|
621
622
|
@@index([lastUtmSource])
|
|
622
623
|
@@index([lastUtmMedium])
|
|
623
624
|
@@index([lastUtmCampaign])
|
|
624
625
|
@@index([lastUtmCampaign, type])
|
|
625
626
|
@@index([lastUtmSource, lastUtmMedium])
|
|
626
|
-
|
|
627
627
|
// Analytics indexes
|
|
628
628
|
@@index([type, timestamp])
|
|
629
629
|
@@index([value])
|
|
630
|
-
|
|
631
630
|
@@map("conversions")
|
|
632
631
|
}
|
|
633
632
|
|
|
@@ -637,28 +636,28 @@ model Conversion {
|
|
|
637
636
|
|
|
638
637
|
// Stripe Customer - polymorphic relation (User OR Organization)
|
|
639
638
|
model StripeCustomer {
|
|
640
|
-
id
|
|
639
|
+
id String @id @default(cuid())
|
|
641
640
|
|
|
642
641
|
// ✅ Polymorphic relation - buď User (B2C) alebo Organization (B2B)
|
|
643
|
-
userId
|
|
644
|
-
organizationId
|
|
642
|
+
userId String? @unique // B2C: Individuálny používateľ
|
|
643
|
+
organizationId String? @unique // B2B: Organizácia
|
|
645
644
|
|
|
646
|
-
stripeCustomerId
|
|
647
|
-
email
|
|
648
|
-
name
|
|
645
|
+
stripeCustomerId String @unique
|
|
646
|
+
email String
|
|
647
|
+
name String?
|
|
649
648
|
|
|
650
649
|
// Billing details (Stripe Address & Tax IDs)
|
|
651
|
-
billingAddress
|
|
652
|
-
taxIds
|
|
650
|
+
billingAddress Json? // Stripe Address object
|
|
651
|
+
taxIds Json? // Stripe Tax IDs array (VAT, etc.)
|
|
653
652
|
|
|
654
|
-
createdAt
|
|
655
|
-
updatedAt
|
|
653
|
+
createdAt DateTime @default(now())
|
|
654
|
+
updatedAt DateTime @updatedAt
|
|
656
655
|
|
|
657
656
|
// Relations
|
|
658
|
-
user
|
|
659
|
-
organization
|
|
660
|
-
subscriptions
|
|
661
|
-
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[]
|
|
662
661
|
|
|
663
662
|
@@index([stripeCustomerId])
|
|
664
663
|
@@index([userId])
|
|
@@ -668,37 +667,37 @@ model StripeCustomer {
|
|
|
668
667
|
|
|
669
668
|
// Stripe Subscription - sledovanie predplatného (User alebo Organization)
|
|
670
669
|
model StripeSubscription {
|
|
671
|
-
id
|
|
670
|
+
id String @id @default(cuid())
|
|
672
671
|
|
|
673
672
|
// ✅ FIX: customerId = FK na StripeCustomer.id (nie stripeCustomerId!)
|
|
674
|
-
customerId String
|
|
675
|
-
stripeCustomerId String
|
|
676
|
-
stripeSubscriptionId String
|
|
673
|
+
customerId String // FK na StripeCustomer.id
|
|
674
|
+
stripeCustomerId String // Stripe ID (pre logging/redundancia)
|
|
675
|
+
stripeSubscriptionId String @unique
|
|
677
676
|
stripePriceId String
|
|
678
677
|
stripeProductId String
|
|
679
678
|
|
|
680
|
-
status
|
|
681
|
-
tier
|
|
682
|
-
billingInterval
|
|
679
|
+
status SubscriptionStatus
|
|
680
|
+
tier SubscriptionTier @default(FREE)
|
|
681
|
+
billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
|
|
683
682
|
|
|
684
|
-
currentPeriodStart
|
|
685
|
-
currentPeriodEnd
|
|
686
|
-
cancelAtPeriodEnd
|
|
687
|
-
canceledAt
|
|
683
|
+
currentPeriodStart DateTime
|
|
684
|
+
currentPeriodEnd DateTime
|
|
685
|
+
cancelAtPeriodEnd Boolean @default(false)
|
|
686
|
+
canceledAt DateTime?
|
|
688
687
|
|
|
689
|
-
trialStart
|
|
690
|
-
trialEnd
|
|
688
|
+
trialStart DateTime?
|
|
689
|
+
trialEnd DateTime?
|
|
691
690
|
|
|
692
691
|
// Additional fields
|
|
693
|
-
quantity
|
|
694
|
-
defaultPaymentMethodId String?
|
|
692
|
+
quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
|
|
693
|
+
defaultPaymentMethodId String? // Stripe payment method ID
|
|
695
694
|
|
|
696
|
-
metadata
|
|
697
|
-
createdAt
|
|
698
|
-
updatedAt
|
|
695
|
+
metadata Json? // Dodatočné Stripe metadata
|
|
696
|
+
createdAt DateTime @default(now())
|
|
697
|
+
updatedAt DateTime @updatedAt
|
|
699
698
|
|
|
700
699
|
// ✅ FIX: Relácia na customerId (interné ID), nie stripeCustomerId (Stripe ID)
|
|
701
|
-
customer
|
|
700
|
+
customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
|
702
701
|
|
|
703
702
|
@@index([stripeSubscriptionId])
|
|
704
703
|
@@index([customerId])
|
|
@@ -711,14 +710,14 @@ model StripeSubscription {
|
|
|
711
710
|
|
|
712
711
|
// Stripe Event - prevencia duplicitného spracovania webhookov (idempotencia)
|
|
713
712
|
model StripeEvent {
|
|
714
|
-
id String
|
|
715
|
-
eventId String
|
|
716
|
-
type String
|
|
717
|
-
data Json
|
|
718
|
-
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)
|
|
719
718
|
processedAt DateTime?
|
|
720
|
-
error String?
|
|
721
|
-
createdAt DateTime
|
|
719
|
+
error String? // Uloženie chýb pri spracovaní
|
|
720
|
+
createdAt DateTime @default(now())
|
|
722
721
|
|
|
723
722
|
@@index([eventId])
|
|
724
723
|
@@index([processed])
|
|
@@ -728,28 +727,28 @@ model StripeEvent {
|
|
|
728
727
|
|
|
729
728
|
// Stripe Payment - sledovanie jednotlivých platieb (voliteľné, pre detailnú analytiku)
|
|
730
729
|
model StripePayment {
|
|
731
|
-
id
|
|
730
|
+
id String @id @default(cuid())
|
|
732
731
|
|
|
733
732
|
// ✅ FIX: Pridaná relácia na StripeCustomer
|
|
734
|
-
customerId
|
|
735
|
-
stripePaymentId
|
|
736
|
-
stripeCustomerId
|
|
733
|
+
customerId String
|
|
734
|
+
stripePaymentId String @unique
|
|
735
|
+
stripeCustomerId String // Redundantné, ale OK pre logging
|
|
737
736
|
|
|
738
|
-
amount
|
|
739
|
-
currency
|
|
740
|
-
status
|
|
741
|
-
paymentMethod
|
|
742
|
-
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?
|
|
743
742
|
|
|
744
743
|
// Invoice info
|
|
745
|
-
invoiceId
|
|
746
|
-
invoiceUrl
|
|
744
|
+
invoiceId String? // Stripe Invoice ID
|
|
745
|
+
invoiceUrl String? // URL na faktúru
|
|
747
746
|
|
|
748
|
-
metadata
|
|
749
|
-
createdAt
|
|
747
|
+
metadata Json?
|
|
748
|
+
createdAt DateTime @default(now())
|
|
750
749
|
|
|
751
750
|
// ✅ Relácia na StripeCustomer
|
|
752
|
-
customer
|
|
751
|
+
customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
|
753
752
|
|
|
754
753
|
@@index([stripePaymentId])
|
|
755
754
|
@@index([customerId])
|
|
@@ -790,35 +789,35 @@ enum ApprovalStatus {
|
|
|
790
789
|
|
|
791
790
|
// Stripe: Status predplatného
|
|
792
791
|
enum SubscriptionStatus {
|
|
793
|
-
ACTIVE
|
|
794
|
-
CANCELED
|
|
795
|
-
INCOMPLETE
|
|
792
|
+
ACTIVE // Predplatné je aktívne
|
|
793
|
+
CANCELED // Predplatné zrušené
|
|
794
|
+
INCOMPLETE // Iniciálna platba zlyhala
|
|
796
795
|
INCOMPLETE_EXPIRED // Neúplné predplatné expirované
|
|
797
|
-
PAST_DUE
|
|
798
|
-
TRIALING
|
|
799
|
-
UNPAID
|
|
800
|
-
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)
|
|
801
800
|
}
|
|
802
801
|
|
|
803
802
|
// Customer type: Individuálny vs SZČO
|
|
804
803
|
enum CustomerType {
|
|
805
|
-
INDIVIDUAL
|
|
806
|
-
SELF_EMPLOYED
|
|
804
|
+
INDIVIDUAL // Fyzická osoba (bez IČO)
|
|
805
|
+
SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
|
|
807
806
|
}
|
|
808
807
|
|
|
809
808
|
// Stripe: Tier predplatného
|
|
810
809
|
enum SubscriptionTier {
|
|
811
|
-
FREE
|
|
812
|
-
LAWYER
|
|
813
|
-
LAWYER_PRO
|
|
814
|
-
LAW_FIRM
|
|
815
|
-
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)
|
|
816
815
|
}
|
|
817
816
|
|
|
818
817
|
// Stripe: Billing interval
|
|
819
818
|
enum BillingInterval {
|
|
820
|
-
MONTHLY
|
|
821
|
-
YEARLY
|
|
819
|
+
MONTHLY // Monthly billing
|
|
820
|
+
YEARLY // Yearly billing (17% discount)
|
|
822
821
|
}
|
|
823
822
|
|
|
824
823
|
// Canvas Document Status
|
|
@@ -830,16 +829,16 @@ enum DocumentStatus {
|
|
|
830
829
|
|
|
831
830
|
// Conversion types for UTM attribution tracking
|
|
832
831
|
enum ConversionType {
|
|
833
|
-
REGISTRATION
|
|
834
|
-
SUBSCRIPTION
|
|
835
|
-
UPGRADE
|
|
836
|
-
RENEWAL
|
|
837
|
-
TRIAL_START
|
|
838
|
-
PURCHASE
|
|
839
|
-
LEAD
|
|
840
|
-
DEMO_REQUEST
|
|
841
|
-
DOWNLOAD
|
|
842
|
-
FORM_SUBMIT
|
|
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
|
|
843
842
|
}
|
|
844
843
|
|
|
845
844
|
// ============================================
|
|
@@ -966,12 +965,12 @@ enum StepType {
|
|
|
966
965
|
|
|
967
966
|
// Email verification tokens
|
|
968
967
|
model VerificationToken {
|
|
969
|
-
id
|
|
970
|
-
userId
|
|
971
|
-
token
|
|
972
|
-
expires
|
|
973
|
-
createdAt
|
|
974
|
-
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)
|
|
975
974
|
|
|
976
975
|
@@index([token])
|
|
977
976
|
@@index([userId])
|
|
@@ -980,12 +979,12 @@ model VerificationToken {
|
|
|
980
979
|
|
|
981
980
|
// Password reset tokens
|
|
982
981
|
model PasswordResetToken {
|
|
983
|
-
id
|
|
984
|
-
userId
|
|
985
|
-
token
|
|
986
|
-
expires
|
|
987
|
-
createdAt
|
|
988
|
-
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)
|
|
989
988
|
|
|
990
989
|
@@index([token])
|
|
991
990
|
@@index([userId])
|
|
@@ -997,21 +996,21 @@ model PasswordResetToken {
|
|
|
997
996
|
// ============================================
|
|
998
997
|
|
|
999
998
|
model CanvasDocument {
|
|
1000
|
-
id String
|
|
999
|
+
id String @id @default(cuid())
|
|
1001
1000
|
userId String
|
|
1002
1001
|
title String
|
|
1003
|
-
status DocumentStatus
|
|
1004
|
-
createdAt DateTime
|
|
1005
|
-
updatedAt DateTime
|
|
1002
|
+
status DocumentStatus @default(DRAFT)
|
|
1003
|
+
createdAt DateTime @default(now())
|
|
1004
|
+
updatedAt DateTime @updatedAt
|
|
1006
1005
|
originConversationId String?
|
|
1007
|
-
originAnswerId String?
|
|
1008
|
-
currentVersionId String?
|
|
1006
|
+
originAnswerId String? // No FK constraint - async save reference
|
|
1007
|
+
currentVersionId String? @unique
|
|
1009
1008
|
|
|
1010
1009
|
// Relations
|
|
1011
|
-
user
|
|
1012
|
-
originConversation
|
|
1013
|
-
currentVersion
|
|
1014
|
-
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")
|
|
1015
1014
|
|
|
1016
1015
|
@@index([userId])
|
|
1017
1016
|
@@index([originConversationId])
|
|
@@ -1020,12 +1019,12 @@ model CanvasDocument {
|
|
|
1020
1019
|
}
|
|
1021
1020
|
|
|
1022
1021
|
model CanvasDocumentVersion {
|
|
1023
|
-
id String
|
|
1022
|
+
id String @id @default(cuid())
|
|
1024
1023
|
documentId String
|
|
1025
1024
|
versionNumber Int
|
|
1026
1025
|
title String
|
|
1027
1026
|
markdownContent String
|
|
1028
|
-
createdAt DateTime
|
|
1027
|
+
createdAt DateTime @default(now())
|
|
1029
1028
|
createdBy String
|
|
1030
1029
|
regenerationPrompt String?
|
|
1031
1030
|
parentVersionId String?
|
|
@@ -1050,34 +1049,34 @@ model CanvasDocumentVersion {
|
|
|
1050
1049
|
// Main folder entity with hierarchy support via parentId
|
|
1051
1050
|
// Uses materialized path pattern for efficient hierarchy queries
|
|
1052
1051
|
model Folder {
|
|
1053
|
-
id String
|
|
1052
|
+
id String @id @default(cuid())
|
|
1054
1053
|
name String
|
|
1055
1054
|
description String?
|
|
1056
|
-
color String?
|
|
1057
|
-
icon String?
|
|
1055
|
+
color String? @default("#6366f1")
|
|
1056
|
+
icon String? @default("folder")
|
|
1058
1057
|
|
|
1059
1058
|
// Ownership (always user-owned)
|
|
1060
|
-
ownerId
|
|
1059
|
+
ownerId String
|
|
1061
1060
|
|
|
1062
1061
|
// Hierarchy
|
|
1063
1062
|
parentId String?
|
|
1064
|
-
rootFolderId String?
|
|
1065
|
-
path String
|
|
1066
|
-
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)
|
|
1067
1066
|
|
|
1068
1067
|
// Metadata
|
|
1069
|
-
sortOrder
|
|
1070
|
-
isArchived
|
|
1071
|
-
createdAt
|
|
1072
|
-
updatedAt
|
|
1068
|
+
sortOrder Int @default(0)
|
|
1069
|
+
isArchived Boolean @default(false)
|
|
1070
|
+
createdAt DateTime @default(now())
|
|
1071
|
+
updatedAt DateTime @updatedAt
|
|
1073
1072
|
|
|
1074
1073
|
// Relations
|
|
1075
|
-
owner
|
|
1076
|
-
parent
|
|
1077
|
-
children
|
|
1078
|
-
items
|
|
1079
|
-
activities
|
|
1080
|
-
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[]
|
|
1081
1080
|
|
|
1082
1081
|
@@index([ownerId])
|
|
1083
1082
|
@@index([parentId])
|
|
@@ -1090,12 +1089,12 @@ model Folder {
|
|
|
1090
1089
|
// Links entities (conversations, documents, etc.) to folders
|
|
1091
1090
|
// Polymorphic: entityType + entityId point to any supported entity
|
|
1092
1091
|
model FolderItem {
|
|
1093
|
-
id
|
|
1094
|
-
folderId
|
|
1092
|
+
id String @id @default(cuid())
|
|
1093
|
+
folderId String
|
|
1095
1094
|
|
|
1096
1095
|
// Polymorphic reference
|
|
1097
|
-
entityType
|
|
1098
|
-
entityId
|
|
1096
|
+
entityType FolderItemType
|
|
1097
|
+
entityId String
|
|
1099
1098
|
|
|
1100
1099
|
// Optional metadata override
|
|
1101
1100
|
displayName String?
|
|
@@ -1105,14 +1104,14 @@ model FolderItem {
|
|
|
1105
1104
|
rootFolderId String?
|
|
1106
1105
|
|
|
1107
1106
|
// Tracking
|
|
1108
|
-
addedById
|
|
1109
|
-
sortOrder
|
|
1110
|
-
createdAt
|
|
1111
|
-
updatedAt
|
|
1107
|
+
addedById String
|
|
1108
|
+
sortOrder Int @default(0)
|
|
1109
|
+
createdAt DateTime @default(now())
|
|
1110
|
+
updatedAt DateTime @updatedAt
|
|
1112
1111
|
|
|
1113
1112
|
// Relations
|
|
1114
|
-
folder
|
|
1115
|
-
addedBy
|
|
1113
|
+
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
|
1114
|
+
addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
|
|
1116
1115
|
|
|
1117
1116
|
@@unique([folderId, entityType, entityId])
|
|
1118
1117
|
@@index([folderId])
|
|
@@ -1124,25 +1123,25 @@ model FolderItem {
|
|
|
1124
1123
|
// Timeline entries for folders (system events + user messages)
|
|
1125
1124
|
// Opening ANY folder shows ALL activities from the entire folder tree
|
|
1126
1125
|
model FolderActivity {
|
|
1127
|
-
id
|
|
1128
|
-
folderId
|
|
1126
|
+
id String @id @default(cuid())
|
|
1127
|
+
folderId String
|
|
1129
1128
|
|
|
1130
1129
|
// Denormalized for cross-folder timeline queries
|
|
1131
|
-
rootFolderId
|
|
1130
|
+
rootFolderId String?
|
|
1132
1131
|
|
|
1133
1132
|
// Activity details
|
|
1134
1133
|
activityType FolderActivityType
|
|
1135
1134
|
userId String
|
|
1136
|
-
content String?
|
|
1137
|
-
metadata Json?
|
|
1138
|
-
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
|
|
1139
1138
|
|
|
1140
1139
|
// Timestamp
|
|
1141
|
-
createdAt
|
|
1140
|
+
createdAt DateTime @default(now())
|
|
1142
1141
|
|
|
1143
1142
|
// Relations
|
|
1144
|
-
folder
|
|
1145
|
-
user
|
|
1143
|
+
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
|
1144
|
+
user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
|
|
1146
1145
|
|
|
1147
1146
|
@@index([folderId])
|
|
1148
1147
|
@@index([rootFolderId])
|
|
@@ -1155,26 +1154,26 @@ model FolderActivity {
|
|
|
1155
1154
|
// Sharing permissions for folders
|
|
1156
1155
|
// Can share with individual user OR entire organization (not both)
|
|
1157
1156
|
model FolderShare {
|
|
1158
|
-
id
|
|
1159
|
-
folderId
|
|
1157
|
+
id String @id @default(cuid())
|
|
1158
|
+
folderId String
|
|
1160
1159
|
|
|
1161
1160
|
// Share target (either user OR organization, enforced by application logic)
|
|
1162
1161
|
userId String?
|
|
1163
1162
|
organizationId String?
|
|
1164
1163
|
|
|
1165
1164
|
// Permission level
|
|
1166
|
-
permission
|
|
1165
|
+
permission FolderPermission @default(VIEW)
|
|
1167
1166
|
|
|
1168
1167
|
// Sharing metadata
|
|
1169
|
-
sharedById
|
|
1170
|
-
sharedAt
|
|
1171
|
-
expiresAt
|
|
1168
|
+
sharedById String
|
|
1169
|
+
sharedAt DateTime @default(now())
|
|
1170
|
+
expiresAt DateTime?
|
|
1172
1171
|
|
|
1173
1172
|
// Relations
|
|
1174
|
-
folder
|
|
1175
|
-
user
|
|
1176
|
-
organization
|
|
1177
|
-
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)
|
|
1178
1177
|
|
|
1179
1178
|
// Partial unique indexes for nullable columns
|
|
1180
1179
|
@@unique([folderId, userId])
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Multi-Brand Support: Allow same email to exist across different brands (smartlex, justi)
|
|
2
|
+
-- Existing data gets brand = 'smartlex' via DEFAULT value
|
|
3
|
+
|
|
4
|
+
-- DropIndex
|
|
5
|
+
DROP INDEX "Account_provider_providerAccountId_key";
|
|
6
|
+
|
|
7
|
+
-- DropIndex
|
|
8
|
+
DROP INDEX "User_email_key";
|
|
9
|
+
|
|
10
|
+
-- AlterTable: Add brand column to Account with default 'smartlex'
|
|
11
|
+
ALTER TABLE "Account" ADD COLUMN "brand" TEXT NOT NULL DEFAULT 'smartlex';
|
|
12
|
+
|
|
13
|
+
-- AlterTable: Add brand column to User with default 'smartlex'
|
|
14
|
+
ALTER TABLE "User" ADD COLUMN "brand" TEXT NOT NULL DEFAULT 'smartlex';
|
|
15
|
+
|
|
16
|
+
-- CreateIndex: Compound unique on Account (provider + providerAccountId + brand)
|
|
17
|
+
CREATE UNIQUE INDEX "Account_provider_providerAccountId_brand_key" ON "Account"("provider", "providerAccountId", "brand");
|
|
18
|
+
|
|
19
|
+
-- CreateIndex: Index on User.brand for fast filtering
|
|
20
|
+
CREATE INDEX "User_brand_idx" ON "User"("brand");
|
|
21
|
+
|
|
22
|
+
-- CreateIndex: Compound unique on User (email + brand) - allows same email in different brands
|
|
23
|
+
CREATE UNIQUE INDEX "User_email_brand_key" ON "User"("email", "brand");
|