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