@danielcok17/prisma-db 1.13.2 → 1.14.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/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +4 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +12 -0
- package/dist/paths.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +13 -0
- package/dist/utils.js.map +1 -0
- package/package.json +1 -1
- package/prisma/app.prisma +343 -341
- package/prisma/migrations/20260215120000_add_multi_brand_support/migration.sql +23 -0
- package/prisma/migrations/20260304120000_add_integrity_hashing/migration.sql +9 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,YAAY,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1E,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.LawPrismaClient = exports.AppPrismaClient = void 0;
|
|
18
|
+
var app_1 = require("../prisma/generated/app");
|
|
19
|
+
Object.defineProperty(exports, "AppPrismaClient", { enumerable: true, get: function () { return app_1.PrismaClient; } });
|
|
20
|
+
var law_1 = require("../prisma/generated/law");
|
|
21
|
+
Object.defineProperty(exports, "LawPrismaClient", { enumerable: true, get: function () { return law_1.PrismaClient; } });
|
|
22
|
+
__exportStar(require("./utils"), exports);
|
|
23
|
+
__exportStar(require("./paths"), exports);
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,+CAA0E;AAAjE,sGAAA,YAAY,OAAmB;AACxC,+CAA0E;AAAjE,sGAAA,YAAY,OAAmB;AACxC,0CAAwB;AACxB,0CAAwB"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,EAAE,MAA0D,CAAC;AAC7F,eAAO,MAAM,mBAAmB,EAAE,MAA0D,CAAC;AAC7F,eAAO,MAAM,oBAAoB,EAAE,MAA0D,CAAC"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.prismaMigrationsPath = exports.lawPrismaSchemaPath = exports.appPrismaSchemaPath = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const packageRootDir = path_1.default.resolve(__dirname, '..');
|
|
9
|
+
exports.appPrismaSchemaPath = path_1.default.join(packageRootDir, 'prisma', 'app.prisma');
|
|
10
|
+
exports.lawPrismaSchemaPath = path_1.default.join(packageRootDir, 'prisma', 'law.prisma');
|
|
11
|
+
exports.prismaMigrationsPath = path_1.default.join(packageRootDir, 'prisma', 'migrations');
|
|
12
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":";;;;;;AAAA,gDAAwB;AAExB,MAAM,cAAc,GAAG,cAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAExC,QAAA,mBAAmB,GAAW,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AAChF,QAAA,mBAAmB,GAAW,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;AAChF,QAAA,oBAAoB,GAAW,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PrismaClient as AppPrismaClient } from '../prisma/generated/app';
|
|
2
|
+
import { PrismaClient as LawPrismaClient } from '../prisma/generated/law';
|
|
3
|
+
export declare const appPrisma: AppPrismaClient<import("../prisma/generated/app").Prisma.PrismaClientOptions, never, import("../prisma/generated/app/runtime/library").DefaultArgs>;
|
|
4
|
+
export declare const lawPrisma: LawPrismaClient<import("../prisma/generated/law").Prisma.PrismaClientOptions, never, import("../prisma/generated/law/runtime/library").DefaultArgs>;
|
|
5
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,YAAY,IAAI,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAO1E,eAAO,MAAM,SAAS,qJAAqD,CAAC;AAC5E,eAAO,MAAM,SAAS,qJAAqD,CAAC"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lawPrisma = exports.appPrisma = void 0;
|
|
4
|
+
const app_1 = require("../prisma/generated/app");
|
|
5
|
+
const law_1 = require("../prisma/generated/law");
|
|
6
|
+
const globalForPrisma = globalThis;
|
|
7
|
+
exports.appPrisma = globalForPrisma.appPrisma ?? new app_1.PrismaClient();
|
|
8
|
+
exports.lawPrisma = globalForPrisma.lawPrisma ?? new law_1.PrismaClient();
|
|
9
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
10
|
+
globalForPrisma.appPrisma = exports.appPrisma;
|
|
11
|
+
globalForPrisma.lawPrisma = exports.lawPrisma;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,iDAA0E;AAC1E,iDAA0E;AAE1E,MAAM,eAAe,GAAG,UAGvB,CAAC;AAEW,QAAA,SAAS,GAAG,eAAe,CAAC,SAAS,IAAI,IAAI,kBAAe,EAAE,CAAC;AAC/D,QAAA,SAAS,GAAG,eAAe,CAAC,SAAS,IAAI,IAAI,kBAAe,EAAE,CAAC;AAE5E,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;IAC1C,eAAe,CAAC,SAAS,GAAG,iBAAS,CAAC;IACtC,eAAe,CAAC,SAAS,GAAG,iBAAS,CAAC;AACxC,CAAC"}
|
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,27 @@ 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)
|
|
255
|
+
integrityHash String? // HMAC-SHA256 hash chain - posledný chainHash pre verifikáciu integrity
|
|
250
256
|
// Relácie
|
|
251
257
|
answers Answer[]
|
|
252
|
-
user User
|
|
258
|
+
user User @relation(fields: [userId], references: [id])
|
|
253
259
|
workflowLogs WorkflowLog[]
|
|
254
260
|
canvasDocuments CanvasDocument[]
|
|
255
261
|
|
|
@@ -259,29 +265,31 @@ model Conversation {
|
|
|
259
265
|
|
|
260
266
|
// Hlavný model - všetky správy, bez duplikátov
|
|
261
267
|
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
|
-
|
|
275
|
-
|
|
268
|
+
id String @id @default(cuid())
|
|
269
|
+
conversationId String
|
|
270
|
+
messageId String @unique
|
|
271
|
+
role Role
|
|
272
|
+
content String
|
|
273
|
+
question String?
|
|
274
|
+
answer String?
|
|
275
|
+
evaluation String?
|
|
276
|
+
isWelcome Boolean @default(false)
|
|
277
|
+
processingTime Int?
|
|
278
|
+
model String?
|
|
279
|
+
userId String?
|
|
280
|
+
contentHash String? // HMAC-SHA256 hash obsahu správy (content + role + messageId)
|
|
281
|
+
chainHash String? // Hash chain - zahŕňa predchádzajúci chainHash pre integritu sekvencie
|
|
282
|
+
createdAt DateTime @default(now())
|
|
283
|
+
updatedAt DateTime @updatedAt
|
|
276
284
|
// Relácie
|
|
277
|
-
conversation
|
|
278
|
-
user
|
|
285
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
286
|
+
user User? @relation(fields: [userId], references: [id])
|
|
279
287
|
feedback Feedback?
|
|
280
288
|
references Reference[]
|
|
281
289
|
metrics AnswerMetrics?
|
|
282
290
|
WorkflowLog WorkflowLog[]
|
|
283
291
|
files MessageFile[]
|
|
284
|
-
canvasDocumentId String?
|
|
292
|
+
canvasDocumentId String? // No FK constraint - flexible document management
|
|
285
293
|
|
|
286
294
|
@@index([conversationId])
|
|
287
295
|
@@index([messageId])
|
|
@@ -409,12 +417,12 @@ model PageView {
|
|
|
409
417
|
}
|
|
410
418
|
|
|
411
419
|
model Session {
|
|
412
|
-
id
|
|
413
|
-
sessionId
|
|
414
|
-
userId
|
|
415
|
-
startedAt
|
|
416
|
-
endedAt
|
|
417
|
-
duration
|
|
420
|
+
id String @id @default(cuid())
|
|
421
|
+
sessionId String @unique
|
|
422
|
+
userId String?
|
|
423
|
+
startedAt DateTime @default(now())
|
|
424
|
+
endedAt DateTime?
|
|
425
|
+
duration Int?
|
|
418
426
|
|
|
419
427
|
// ============================================
|
|
420
428
|
// FIRST-TOUCH ATTRIBUTION
|
|
@@ -427,7 +435,7 @@ model Session {
|
|
|
427
435
|
firstUtmCampaign String?
|
|
428
436
|
firstUtmContent String?
|
|
429
437
|
firstUtmTerm String?
|
|
430
|
-
firstTouchAt DateTime?
|
|
438
|
+
firstTouchAt DateTime? // Timestamp prvého UTM touchpoint
|
|
431
439
|
|
|
432
440
|
// ============================================
|
|
433
441
|
// LAST-TOUCH ATTRIBUTION
|
|
@@ -440,14 +448,14 @@ model Session {
|
|
|
440
448
|
lastUtmCampaign String?
|
|
441
449
|
lastUtmContent String?
|
|
442
450
|
lastUtmTerm String?
|
|
443
|
-
lastTouchAt DateTime?
|
|
451
|
+
lastTouchAt DateTime? // Timestamp posledného UTM touchpoint
|
|
444
452
|
|
|
445
453
|
// ============================================
|
|
446
454
|
// RELATIONS
|
|
447
455
|
// ============================================
|
|
448
456
|
|
|
449
|
-
touchPoints
|
|
450
|
-
conversions
|
|
457
|
+
touchPoints TouchPoint[] // Všetky touchpointy v session
|
|
458
|
+
conversions Conversion[] // Všetky konverzie v session
|
|
451
459
|
|
|
452
460
|
// Existujúce relations
|
|
453
461
|
pageViews PageView[]
|
|
@@ -462,7 +470,6 @@ model Session {
|
|
|
462
470
|
@@index([lastUtmCampaign])
|
|
463
471
|
@@index([firstUtmSource, firstUtmMedium])
|
|
464
472
|
@@index([lastUtmSource, lastUtmMedium])
|
|
465
|
-
|
|
466
473
|
// Existujúce indexes
|
|
467
474
|
@@index([userId])
|
|
468
475
|
@@index([startedAt])
|
|
@@ -475,47 +482,47 @@ model Session {
|
|
|
475
482
|
// TouchPoint - každý click s UTM parametrami
|
|
476
483
|
// Umožňuje multi-touch attribution a customer journey analysis
|
|
477
484
|
model TouchPoint {
|
|
478
|
-
id String
|
|
479
|
-
userId String?
|
|
480
|
-
sessionId String
|
|
485
|
+
id String @id @default(cuid())
|
|
486
|
+
userId String? // Pre prihlásených používateľov
|
|
487
|
+
sessionId String // Session tracking (required)
|
|
481
488
|
|
|
482
489
|
// ============================================
|
|
483
490
|
// UTM PARAMETERS (všetky lowercase!)
|
|
484
491
|
// ============================================
|
|
485
492
|
// Best practice: Vždy lowercase, validované pred uložením
|
|
486
493
|
|
|
487
|
-
utmSource String?
|
|
488
|
-
utmMedium String?
|
|
489
|
-
utmCampaign String?
|
|
490
|
-
utmContent String?
|
|
491
|
-
utmTerm String?
|
|
494
|
+
utmSource String? // Odkiaľ: "email", "facebook", "google", "linkedin"
|
|
495
|
+
utmMedium String? // Typ média: "email", "cpc", "social", "organic", "paid-social"
|
|
496
|
+
utmCampaign String? // Názov kampane: "smizany-2026-01", "spring-sale-2026"
|
|
497
|
+
utmContent String? // Variant/link identifier: "cta-primary", "banner-top", "hero-button"
|
|
498
|
+
utmTerm String? // Keywords (pre paid search): "pravny-asistent", "advokat-ai"
|
|
492
499
|
|
|
493
500
|
// ============================================
|
|
494
501
|
// CONTEXT & METADATA
|
|
495
502
|
// ============================================
|
|
496
503
|
|
|
497
|
-
page
|
|
498
|
-
path
|
|
499
|
-
referrer
|
|
504
|
+
page String // Ktorá stránka: "/", "/pricing", "/contact"
|
|
505
|
+
path String // Celý path s params: "/?utm_source=email&utm_campaign=..."
|
|
506
|
+
referrer String? // HTTP Referrer (odkiaľ prišiel)
|
|
500
507
|
|
|
501
508
|
// Device & Browser info (denormalizované pre rýchle queries)
|
|
502
|
-
deviceType String?
|
|
503
|
-
browser String?
|
|
504
|
-
os String?
|
|
509
|
+
deviceType String? // "mobile", "tablet", "desktop"
|
|
510
|
+
browser String? // "Chrome", "Firefox", "Safari"
|
|
511
|
+
os String? // "Windows", "macOS", "iOS", "Android"
|
|
505
512
|
|
|
506
513
|
// Geolocation (anonymizované)
|
|
507
|
-
country
|
|
508
|
-
city
|
|
514
|
+
country String? // "SK", "CZ", "US"
|
|
515
|
+
city String? // "Bratislava", "Prague"
|
|
509
516
|
|
|
510
517
|
// Timestamp
|
|
511
|
-
timestamp
|
|
518
|
+
timestamp DateTime @default(now())
|
|
512
519
|
|
|
513
520
|
// ============================================
|
|
514
521
|
// RELATIONS
|
|
515
522
|
// ============================================
|
|
516
523
|
|
|
517
|
-
session
|
|
518
|
-
user
|
|
524
|
+
session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
|
|
525
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
519
526
|
|
|
520
527
|
// ============================================
|
|
521
528
|
// INDEXES (optimalizované pre analytics queries)
|
|
@@ -531,38 +538,37 @@ model TouchPoint {
|
|
|
531
538
|
@@index([timestamp])
|
|
532
539
|
@@index([sessionId, timestamp])
|
|
533
540
|
@@index([userId, timestamp])
|
|
534
|
-
|
|
535
541
|
@@map("touch_points")
|
|
536
542
|
}
|
|
537
543
|
|
|
538
544
|
// Conversion - sledovanie konverzií s attribution
|
|
539
545
|
// Podporuje first-touch, last-touch aj multi-touch attribution
|
|
540
546
|
model Conversion {
|
|
541
|
-
id
|
|
542
|
-
userId
|
|
543
|
-
sessionId
|
|
547
|
+
id String @id @default(cuid())
|
|
548
|
+
userId String? // Kto konvertoval (nullable pre anonymous conversions)
|
|
549
|
+
sessionId String // V ktorej session
|
|
544
550
|
|
|
545
551
|
// ============================================
|
|
546
552
|
// CONVERSION METADATA
|
|
547
553
|
// ============================================
|
|
548
554
|
|
|
549
|
-
type
|
|
550
|
-
value
|
|
551
|
-
currency
|
|
555
|
+
type ConversionType // Typ konverzie
|
|
556
|
+
value Decimal? // Revenue (pre e-commerce/subscriptions)
|
|
557
|
+
currency String? @default("EUR")
|
|
552
558
|
|
|
553
559
|
// Optional metadata
|
|
554
|
-
metadata
|
|
560
|
+
metadata Json? // Stripe subscription ID, product info, etc.
|
|
555
561
|
|
|
556
562
|
// ============================================
|
|
557
563
|
// FIRST-TOUCH ATTRIBUTION (40% credit)
|
|
558
564
|
// ============================================
|
|
559
565
|
// Prvý UTM touchpoint v session = awareness channel
|
|
560
566
|
|
|
561
|
-
firstUtmSource String?
|
|
562
|
-
firstUtmMedium String?
|
|
563
|
-
firstUtmCampaign String?
|
|
564
|
-
firstUtmContent String?
|
|
565
|
-
firstUtmTerm String?
|
|
567
|
+
firstUtmSource String? // Odkiaľ prišiel prvýkrát
|
|
568
|
+
firstUtmMedium String? // Akým médiom prvýkrát
|
|
569
|
+
firstUtmCampaign String? // Ktorá kampaň priniesla awareness
|
|
570
|
+
firstUtmContent String? // Ktorý konkrétny link/banner
|
|
571
|
+
firstUtmTerm String? // Ktoré keywords (pre paid search)
|
|
566
572
|
firstTouchAt DateTime? // Kedy bol prvý touchpoint
|
|
567
573
|
|
|
568
574
|
// ============================================
|
|
@@ -570,11 +576,11 @@ model Conversion {
|
|
|
570
576
|
// ============================================
|
|
571
577
|
// Posledný UTM touchpoint pred konverziou = conversion channel
|
|
572
578
|
|
|
573
|
-
lastUtmSource String?
|
|
574
|
-
lastUtmMedium String?
|
|
575
|
-
lastUtmCampaign String?
|
|
576
|
-
lastUtmContent String?
|
|
577
|
-
lastUtmTerm String?
|
|
579
|
+
lastUtmSource String? // Odkiaľ prišiel naposledy
|
|
580
|
+
lastUtmMedium String? // Akým médiom naposledy
|
|
581
|
+
lastUtmCampaign String? // Ktorá kampaň priniesla konverziu
|
|
582
|
+
lastUtmContent String? // Ktorý konkrétny link/banner
|
|
583
|
+
lastUtmTerm String? // Ktoré keywords (pre paid search)
|
|
578
584
|
lastTouchAt DateTime? // Kedy bol posledný touchpoint
|
|
579
585
|
|
|
580
586
|
// ============================================
|
|
@@ -582,24 +588,24 @@ model Conversion {
|
|
|
582
588
|
// ============================================
|
|
583
589
|
// Všetky touchpointy medzi prvým a posledným
|
|
584
590
|
|
|
585
|
-
touchPointIds
|
|
586
|
-
touchPointCount
|
|
591
|
+
touchPointIds String[] // Array of TouchPoint IDs (pre detailnú analýzu)
|
|
592
|
+
touchPointCount Int? // Počet touchpoints v customer journey
|
|
587
593
|
|
|
588
594
|
// ============================================
|
|
589
595
|
// CALCULATED FIELDS
|
|
590
596
|
// ============================================
|
|
591
597
|
|
|
592
|
-
journeyDuration
|
|
598
|
+
journeyDuration Int? // Čas od prvého touchpoint po konverziu (sekundy)
|
|
593
599
|
|
|
594
600
|
// Timestamp
|
|
595
|
-
timestamp
|
|
601
|
+
timestamp DateTime @default(now())
|
|
596
602
|
|
|
597
603
|
// ============================================
|
|
598
604
|
// RELATIONS
|
|
599
605
|
// ============================================
|
|
600
606
|
|
|
601
|
-
session
|
|
602
|
-
user
|
|
607
|
+
session Session @relation(fields: [sessionId], references: [sessionId], onDelete: Cascade)
|
|
608
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
603
609
|
|
|
604
610
|
// ============================================
|
|
605
611
|
// INDEXES
|
|
@@ -609,25 +615,21 @@ model Conversion {
|
|
|
609
615
|
@@index([sessionId])
|
|
610
616
|
@@index([type])
|
|
611
617
|
@@index([timestamp])
|
|
612
|
-
|
|
613
618
|
// First-touch attribution indexes
|
|
614
619
|
@@index([firstUtmSource])
|
|
615
620
|
@@index([firstUtmMedium])
|
|
616
621
|
@@index([firstUtmCampaign])
|
|
617
622
|
@@index([firstUtmCampaign, type])
|
|
618
623
|
@@index([firstUtmSource, firstUtmMedium])
|
|
619
|
-
|
|
620
624
|
// Last-touch attribution indexes
|
|
621
625
|
@@index([lastUtmSource])
|
|
622
626
|
@@index([lastUtmMedium])
|
|
623
627
|
@@index([lastUtmCampaign])
|
|
624
628
|
@@index([lastUtmCampaign, type])
|
|
625
629
|
@@index([lastUtmSource, lastUtmMedium])
|
|
626
|
-
|
|
627
630
|
// Analytics indexes
|
|
628
631
|
@@index([type, timestamp])
|
|
629
632
|
@@index([value])
|
|
630
|
-
|
|
631
633
|
@@map("conversions")
|
|
632
634
|
}
|
|
633
635
|
|
|
@@ -637,28 +639,28 @@ model Conversion {
|
|
|
637
639
|
|
|
638
640
|
// Stripe Customer - polymorphic relation (User OR Organization)
|
|
639
641
|
model StripeCustomer {
|
|
640
|
-
id
|
|
642
|
+
id String @id @default(cuid())
|
|
641
643
|
|
|
642
644
|
// ✅ Polymorphic relation - buď User (B2C) alebo Organization (B2B)
|
|
643
|
-
userId
|
|
644
|
-
organizationId
|
|
645
|
+
userId String? @unique // B2C: Individuálny používateľ
|
|
646
|
+
organizationId String? @unique // B2B: Organizácia
|
|
645
647
|
|
|
646
|
-
stripeCustomerId
|
|
647
|
-
email
|
|
648
|
-
name
|
|
648
|
+
stripeCustomerId String @unique
|
|
649
|
+
email String
|
|
650
|
+
name String?
|
|
649
651
|
|
|
650
652
|
// Billing details (Stripe Address & Tax IDs)
|
|
651
|
-
billingAddress
|
|
652
|
-
taxIds
|
|
653
|
+
billingAddress Json? // Stripe Address object
|
|
654
|
+
taxIds Json? // Stripe Tax IDs array (VAT, etc.)
|
|
653
655
|
|
|
654
|
-
createdAt
|
|
655
|
-
updatedAt
|
|
656
|
+
createdAt DateTime @default(now())
|
|
657
|
+
updatedAt DateTime @updatedAt
|
|
656
658
|
|
|
657
659
|
// Relations
|
|
658
|
-
user
|
|
659
|
-
organization
|
|
660
|
-
subscriptions
|
|
661
|
-
payments
|
|
660
|
+
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
661
|
+
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
662
|
+
subscriptions StripeSubscription[]
|
|
663
|
+
payments StripePayment[]
|
|
662
664
|
|
|
663
665
|
@@index([stripeCustomerId])
|
|
664
666
|
@@index([userId])
|
|
@@ -668,37 +670,37 @@ model StripeCustomer {
|
|
|
668
670
|
|
|
669
671
|
// Stripe Subscription - sledovanie predplatného (User alebo Organization)
|
|
670
672
|
model StripeSubscription {
|
|
671
|
-
id
|
|
673
|
+
id String @id @default(cuid())
|
|
672
674
|
|
|
673
675
|
// ✅ FIX: customerId = FK na StripeCustomer.id (nie stripeCustomerId!)
|
|
674
|
-
customerId String
|
|
675
|
-
stripeCustomerId String
|
|
676
|
-
stripeSubscriptionId String
|
|
676
|
+
customerId String // FK na StripeCustomer.id
|
|
677
|
+
stripeCustomerId String // Stripe ID (pre logging/redundancia)
|
|
678
|
+
stripeSubscriptionId String @unique
|
|
677
679
|
stripePriceId String
|
|
678
680
|
stripeProductId String
|
|
679
681
|
|
|
680
|
-
status
|
|
681
|
-
tier
|
|
682
|
-
billingInterval
|
|
682
|
+
status SubscriptionStatus
|
|
683
|
+
tier SubscriptionTier @default(FREE)
|
|
684
|
+
billingInterval BillingInterval @default(MONTHLY) // Monthly or Yearly billing
|
|
683
685
|
|
|
684
|
-
currentPeriodStart
|
|
685
|
-
currentPeriodEnd
|
|
686
|
-
cancelAtPeriodEnd
|
|
687
|
-
canceledAt
|
|
686
|
+
currentPeriodStart DateTime
|
|
687
|
+
currentPeriodEnd DateTime
|
|
688
|
+
cancelAtPeriodEnd Boolean @default(false)
|
|
689
|
+
canceledAt DateTime?
|
|
688
690
|
|
|
689
|
-
trialStart
|
|
690
|
-
trialEnd
|
|
691
|
+
trialStart DateTime?
|
|
692
|
+
trialEnd DateTime?
|
|
691
693
|
|
|
692
694
|
// Additional fields
|
|
693
|
-
quantity
|
|
694
|
-
defaultPaymentMethodId String?
|
|
695
|
+
quantity Int @default(1) // Počet seats (pre LAW_FIRM/ENTERPRISE tier)
|
|
696
|
+
defaultPaymentMethodId String? // Stripe payment method ID
|
|
695
697
|
|
|
696
|
-
metadata
|
|
697
|
-
createdAt
|
|
698
|
-
updatedAt
|
|
698
|
+
metadata Json? // Dodatočné Stripe metadata
|
|
699
|
+
createdAt DateTime @default(now())
|
|
700
|
+
updatedAt DateTime @updatedAt
|
|
699
701
|
|
|
700
702
|
// ✅ FIX: Relácia na customerId (interné ID), nie stripeCustomerId (Stripe ID)
|
|
701
|
-
customer
|
|
703
|
+
customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
|
702
704
|
|
|
703
705
|
@@index([stripeSubscriptionId])
|
|
704
706
|
@@index([customerId])
|
|
@@ -711,14 +713,14 @@ model StripeSubscription {
|
|
|
711
713
|
|
|
712
714
|
// Stripe Event - prevencia duplicitného spracovania webhookov (idempotencia)
|
|
713
715
|
model StripeEvent {
|
|
714
|
-
id String
|
|
715
|
-
eventId String
|
|
716
|
-
type String
|
|
717
|
-
data Json
|
|
718
|
-
processed Boolean
|
|
716
|
+
id String @id @default(cuid())
|
|
717
|
+
eventId String @unique // Stripe event ID
|
|
718
|
+
type String // Typ eventu (napr. 'checkout.session.completed')
|
|
719
|
+
data Json // Úplné event data pre debugging
|
|
720
|
+
processed Boolean @default(false)
|
|
719
721
|
processedAt DateTime?
|
|
720
|
-
error String?
|
|
721
|
-
createdAt DateTime
|
|
722
|
+
error String? // Uloženie chýb pri spracovaní
|
|
723
|
+
createdAt DateTime @default(now())
|
|
722
724
|
|
|
723
725
|
@@index([eventId])
|
|
724
726
|
@@index([processed])
|
|
@@ -728,28 +730,28 @@ model StripeEvent {
|
|
|
728
730
|
|
|
729
731
|
// Stripe Payment - sledovanie jednotlivých platieb (voliteľné, pre detailnú analytiku)
|
|
730
732
|
model StripePayment {
|
|
731
|
-
id
|
|
733
|
+
id String @id @default(cuid())
|
|
732
734
|
|
|
733
735
|
// ✅ FIX: Pridaná relácia na StripeCustomer
|
|
734
|
-
customerId
|
|
735
|
-
stripePaymentId
|
|
736
|
-
stripeCustomerId
|
|
736
|
+
customerId String
|
|
737
|
+
stripePaymentId String @unique
|
|
738
|
+
stripeCustomerId String // Redundantné, ale OK pre logging
|
|
737
739
|
|
|
738
|
-
amount
|
|
739
|
-
currency
|
|
740
|
-
status
|
|
741
|
-
paymentMethod
|
|
742
|
-
description
|
|
740
|
+
amount Int // Suma v centoch
|
|
741
|
+
currency String @default("eur")
|
|
742
|
+
status String // succeeded, failed, pending
|
|
743
|
+
paymentMethod String? // card, sepa_debit, atď.
|
|
744
|
+
description String?
|
|
743
745
|
|
|
744
746
|
// Invoice info
|
|
745
|
-
invoiceId
|
|
746
|
-
invoiceUrl
|
|
747
|
+
invoiceId String? // Stripe Invoice ID
|
|
748
|
+
invoiceUrl String? // URL na faktúru
|
|
747
749
|
|
|
748
|
-
metadata
|
|
749
|
-
createdAt
|
|
750
|
+
metadata Json?
|
|
751
|
+
createdAt DateTime @default(now())
|
|
750
752
|
|
|
751
753
|
// ✅ Relácia na StripeCustomer
|
|
752
|
-
customer
|
|
754
|
+
customer StripeCustomer @relation(fields: [customerId], references: [id], onDelete: Cascade)
|
|
753
755
|
|
|
754
756
|
@@index([stripePaymentId])
|
|
755
757
|
@@index([customerId])
|
|
@@ -791,35 +793,35 @@ enum ApprovalStatus {
|
|
|
791
793
|
|
|
792
794
|
// Stripe: Status predplatného
|
|
793
795
|
enum SubscriptionStatus {
|
|
794
|
-
ACTIVE
|
|
795
|
-
CANCELED
|
|
796
|
-
INCOMPLETE
|
|
796
|
+
ACTIVE // Predplatné je aktívne
|
|
797
|
+
CANCELED // Predplatné zrušené
|
|
798
|
+
INCOMPLETE // Iniciálna platba zlyhala
|
|
797
799
|
INCOMPLETE_EXPIRED // Neúplné predplatné expirované
|
|
798
|
-
PAST_DUE
|
|
799
|
-
TRIALING
|
|
800
|
-
UNPAID
|
|
801
|
-
PAUSED
|
|
800
|
+
PAST_DUE // Platba zlyhala, retry prebieha
|
|
801
|
+
TRIALING // V skúšobnom období
|
|
802
|
+
UNPAID // Platba zlyhala, žiadny retry
|
|
803
|
+
PAUSED // Predplatné pozastavené (Stripe feature)
|
|
802
804
|
}
|
|
803
805
|
|
|
804
806
|
// Customer type: Individuálny vs SZČO
|
|
805
807
|
enum CustomerType {
|
|
806
|
-
INDIVIDUAL
|
|
807
|
-
SELF_EMPLOYED
|
|
808
|
+
INDIVIDUAL // Fyzická osoba (bez IČO)
|
|
809
|
+
SELF_EMPLOYED // SZČO - Samostatne zárobkovo činná osoba (s IČO)
|
|
808
810
|
}
|
|
809
811
|
|
|
810
812
|
// Stripe: Tier predplatného
|
|
811
813
|
enum SubscriptionTier {
|
|
812
|
-
FREE
|
|
813
|
-
LAWYER
|
|
814
|
-
LAWYER_PRO
|
|
815
|
-
LAW_FIRM
|
|
816
|
-
ENTERPRISE
|
|
814
|
+
FREE // Free tier (10 messages/month)
|
|
815
|
+
LAWYER // Lawyer tier (1000 messages/month, €29/month, 1 user)
|
|
816
|
+
LAWYER_PRO // Lawyer Pro tier (3000 messages/month, €49/month, 1 user) - RECOMMENDED
|
|
817
|
+
LAW_FIRM // Law Firm tier (10000 messages/month, €129/month, up to 5 users)
|
|
818
|
+
ENTERPRISE // Enterprise tier (unlimited messages, unlimited users, custom pricing)
|
|
817
819
|
}
|
|
818
820
|
|
|
819
821
|
// Stripe: Billing interval
|
|
820
822
|
enum BillingInterval {
|
|
821
|
-
MONTHLY
|
|
822
|
-
YEARLY
|
|
823
|
+
MONTHLY // Monthly billing
|
|
824
|
+
YEARLY // Yearly billing (17% discount)
|
|
823
825
|
}
|
|
824
826
|
|
|
825
827
|
// Canvas Document Status
|
|
@@ -831,16 +833,16 @@ enum DocumentStatus {
|
|
|
831
833
|
|
|
832
834
|
// Conversion types for UTM attribution tracking
|
|
833
835
|
enum ConversionType {
|
|
834
|
-
REGISTRATION
|
|
835
|
-
SUBSCRIPTION
|
|
836
|
-
UPGRADE
|
|
837
|
-
RENEWAL
|
|
838
|
-
TRIAL_START
|
|
839
|
-
PURCHASE
|
|
840
|
-
LEAD
|
|
841
|
-
DEMO_REQUEST
|
|
842
|
-
DOWNLOAD
|
|
843
|
-
FORM_SUBMIT
|
|
836
|
+
REGISTRATION // Nová registrácia
|
|
837
|
+
SUBSCRIPTION // Začiatok predplatného (FREE → PAID)
|
|
838
|
+
UPGRADE // Upgrade tieru (LAWYER → LAWYER_PRO)
|
|
839
|
+
RENEWAL // Obnovenie predplatného
|
|
840
|
+
TRIAL_START // Začiatok trial periodu
|
|
841
|
+
PURCHASE // Jednorazový nákup
|
|
842
|
+
LEAD // Vyplnenie kontaktného formulára
|
|
843
|
+
DEMO_REQUEST // Žiadosť o demo
|
|
844
|
+
DOWNLOAD // Stiahnutie resourceu
|
|
845
|
+
FORM_SUBMIT // Iné formuláre
|
|
844
846
|
}
|
|
845
847
|
|
|
846
848
|
// ============================================
|
|
@@ -967,12 +969,12 @@ enum StepType {
|
|
|
967
969
|
|
|
968
970
|
// Email verification tokens
|
|
969
971
|
model VerificationToken {
|
|
970
|
-
id
|
|
971
|
-
userId
|
|
972
|
-
token
|
|
973
|
-
expires
|
|
974
|
-
createdAt
|
|
975
|
-
user
|
|
972
|
+
id String @id @default(cuid())
|
|
973
|
+
userId String
|
|
974
|
+
token String @unique
|
|
975
|
+
expires DateTime
|
|
976
|
+
createdAt DateTime @default(now())
|
|
977
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
976
978
|
|
|
977
979
|
@@index([token])
|
|
978
980
|
@@index([userId])
|
|
@@ -981,12 +983,12 @@ model VerificationToken {
|
|
|
981
983
|
|
|
982
984
|
// Password reset tokens
|
|
983
985
|
model PasswordResetToken {
|
|
984
|
-
id
|
|
985
|
-
userId
|
|
986
|
-
token
|
|
987
|
-
expires
|
|
988
|
-
createdAt
|
|
989
|
-
user
|
|
986
|
+
id String @id @default(cuid())
|
|
987
|
+
userId String
|
|
988
|
+
token String @unique
|
|
989
|
+
expires DateTime
|
|
990
|
+
createdAt DateTime @default(now())
|
|
991
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
990
992
|
|
|
991
993
|
@@index([token])
|
|
992
994
|
@@index([userId])
|
|
@@ -998,21 +1000,21 @@ model PasswordResetToken {
|
|
|
998
1000
|
// ============================================
|
|
999
1001
|
|
|
1000
1002
|
model CanvasDocument {
|
|
1001
|
-
id String
|
|
1003
|
+
id String @id @default(cuid())
|
|
1002
1004
|
userId String
|
|
1003
1005
|
title String
|
|
1004
|
-
status DocumentStatus
|
|
1005
|
-
createdAt DateTime
|
|
1006
|
-
updatedAt DateTime
|
|
1006
|
+
status DocumentStatus @default(DRAFT)
|
|
1007
|
+
createdAt DateTime @default(now())
|
|
1008
|
+
updatedAt DateTime @updatedAt
|
|
1007
1009
|
originConversationId String?
|
|
1008
|
-
originAnswerId String?
|
|
1009
|
-
currentVersionId String?
|
|
1010
|
+
originAnswerId String? // No FK constraint - async save reference
|
|
1011
|
+
currentVersionId String? @unique
|
|
1010
1012
|
|
|
1011
1013
|
// Relations
|
|
1012
|
-
user
|
|
1013
|
-
originConversation
|
|
1014
|
-
currentVersion
|
|
1015
|
-
versions
|
|
1014
|
+
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
|
|
1015
|
+
originConversation Conversation? @relation(fields: [originConversationId], references: [id], onDelete: SetNull)
|
|
1016
|
+
currentVersion CanvasDocumentVersion? @relation("CurrentVersion", fields: [currentVersionId], references: [id], onDelete: SetNull)
|
|
1017
|
+
versions CanvasDocumentVersion[] @relation("DocumentVersions")
|
|
1016
1018
|
|
|
1017
1019
|
@@index([userId])
|
|
1018
1020
|
@@index([originConversationId])
|
|
@@ -1021,12 +1023,12 @@ model CanvasDocument {
|
|
|
1021
1023
|
}
|
|
1022
1024
|
|
|
1023
1025
|
model CanvasDocumentVersion {
|
|
1024
|
-
id String
|
|
1026
|
+
id String @id @default(cuid())
|
|
1025
1027
|
documentId String
|
|
1026
1028
|
versionNumber Int
|
|
1027
1029
|
title String
|
|
1028
1030
|
markdownContent String
|
|
1029
|
-
createdAt DateTime
|
|
1031
|
+
createdAt DateTime @default(now())
|
|
1030
1032
|
createdBy String
|
|
1031
1033
|
regenerationPrompt String?
|
|
1032
1034
|
parentVersionId String?
|
|
@@ -1051,34 +1053,34 @@ model CanvasDocumentVersion {
|
|
|
1051
1053
|
// Main folder entity with hierarchy support via parentId
|
|
1052
1054
|
// Uses materialized path pattern for efficient hierarchy queries
|
|
1053
1055
|
model Folder {
|
|
1054
|
-
id String
|
|
1056
|
+
id String @id @default(cuid())
|
|
1055
1057
|
name String
|
|
1056
1058
|
description String?
|
|
1057
|
-
color String?
|
|
1058
|
-
icon String?
|
|
1059
|
+
color String? @default("#6366f1")
|
|
1060
|
+
icon String? @default("folder")
|
|
1059
1061
|
|
|
1060
1062
|
// Ownership (always user-owned)
|
|
1061
|
-
ownerId
|
|
1063
|
+
ownerId String
|
|
1062
1064
|
|
|
1063
1065
|
// Hierarchy
|
|
1064
1066
|
parentId String?
|
|
1065
|
-
rootFolderId String?
|
|
1066
|
-
path String
|
|
1067
|
-
depth Int
|
|
1067
|
+
rootFolderId String? // NULL for root folders, points to top-level ancestor
|
|
1068
|
+
path String @default("/") // Materialized path: "/parentId/grandparentId/..."
|
|
1069
|
+
depth Int @default(0)
|
|
1068
1070
|
|
|
1069
1071
|
// Metadata
|
|
1070
|
-
sortOrder
|
|
1071
|
-
isArchived
|
|
1072
|
-
createdAt
|
|
1073
|
-
updatedAt
|
|
1072
|
+
sortOrder Int @default(0)
|
|
1073
|
+
isArchived Boolean @default(false)
|
|
1074
|
+
createdAt DateTime @default(now())
|
|
1075
|
+
updatedAt DateTime @updatedAt
|
|
1074
1076
|
|
|
1075
1077
|
// Relations
|
|
1076
|
-
owner
|
|
1077
|
-
parent
|
|
1078
|
-
children
|
|
1079
|
-
items
|
|
1080
|
-
activities
|
|
1081
|
-
shares
|
|
1078
|
+
owner User @relation("FolderOwner", fields: [ownerId], references: [id], onDelete: Cascade)
|
|
1079
|
+
parent Folder? @relation("FolderHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
|
|
1080
|
+
children Folder[] @relation("FolderHierarchy")
|
|
1081
|
+
items FolderItem[]
|
|
1082
|
+
activities FolderActivity[]
|
|
1083
|
+
shares FolderShare[]
|
|
1082
1084
|
|
|
1083
1085
|
@@index([ownerId])
|
|
1084
1086
|
@@index([parentId])
|
|
@@ -1091,12 +1093,12 @@ model Folder {
|
|
|
1091
1093
|
// Links entities (conversations, documents, etc.) to folders
|
|
1092
1094
|
// Polymorphic: entityType + entityId point to any supported entity
|
|
1093
1095
|
model FolderItem {
|
|
1094
|
-
id
|
|
1095
|
-
folderId
|
|
1096
|
+
id String @id @default(cuid())
|
|
1097
|
+
folderId String
|
|
1096
1098
|
|
|
1097
1099
|
// Polymorphic reference
|
|
1098
|
-
entityType
|
|
1099
|
-
entityId
|
|
1100
|
+
entityType FolderItemType
|
|
1101
|
+
entityId String
|
|
1100
1102
|
|
|
1101
1103
|
// Optional metadata override
|
|
1102
1104
|
displayName String?
|
|
@@ -1106,14 +1108,14 @@ model FolderItem {
|
|
|
1106
1108
|
rootFolderId String?
|
|
1107
1109
|
|
|
1108
1110
|
// Tracking
|
|
1109
|
-
addedById
|
|
1110
|
-
sortOrder
|
|
1111
|
-
createdAt
|
|
1112
|
-
updatedAt
|
|
1111
|
+
addedById String
|
|
1112
|
+
sortOrder Int @default(0)
|
|
1113
|
+
createdAt DateTime @default(now())
|
|
1114
|
+
updatedAt DateTime @updatedAt
|
|
1113
1115
|
|
|
1114
1116
|
// Relations
|
|
1115
|
-
folder
|
|
1116
|
-
addedBy
|
|
1117
|
+
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
|
1118
|
+
addedBy User @relation("FolderItemAdder", fields: [addedById], references: [id], onDelete: Restrict)
|
|
1117
1119
|
|
|
1118
1120
|
@@unique([folderId, entityType, entityId])
|
|
1119
1121
|
@@index([folderId])
|
|
@@ -1125,25 +1127,25 @@ model FolderItem {
|
|
|
1125
1127
|
// Timeline entries for folders (system events + user messages)
|
|
1126
1128
|
// Opening ANY folder shows ALL activities from the entire folder tree
|
|
1127
1129
|
model FolderActivity {
|
|
1128
|
-
id
|
|
1129
|
-
folderId
|
|
1130
|
+
id String @id @default(cuid())
|
|
1131
|
+
folderId String
|
|
1130
1132
|
|
|
1131
1133
|
// Denormalized for cross-folder timeline queries
|
|
1132
|
-
rootFolderId
|
|
1134
|
+
rootFolderId String?
|
|
1133
1135
|
|
|
1134
1136
|
// Activity details
|
|
1135
1137
|
activityType FolderActivityType
|
|
1136
1138
|
userId String
|
|
1137
|
-
content String?
|
|
1138
|
-
metadata Json?
|
|
1139
|
-
relatedItemId String?
|
|
1139
|
+
content String? // For USER_MESSAGE, USER_NOTE types
|
|
1140
|
+
metadata Json? // For system event details
|
|
1141
|
+
relatedItemId String? // Optional reference to FolderItem
|
|
1140
1142
|
|
|
1141
1143
|
// Timestamp
|
|
1142
|
-
createdAt
|
|
1144
|
+
createdAt DateTime @default(now())
|
|
1143
1145
|
|
|
1144
1146
|
// Relations
|
|
1145
|
-
folder
|
|
1146
|
-
user
|
|
1147
|
+
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
|
1148
|
+
user User @relation("FolderActivityUser", fields: [userId], references: [id], onDelete: Restrict)
|
|
1147
1149
|
|
|
1148
1150
|
@@index([folderId])
|
|
1149
1151
|
@@index([rootFolderId])
|
|
@@ -1156,26 +1158,26 @@ model FolderActivity {
|
|
|
1156
1158
|
// Sharing permissions for folders
|
|
1157
1159
|
// Can share with individual user OR entire organization (not both)
|
|
1158
1160
|
model FolderShare {
|
|
1159
|
-
id
|
|
1160
|
-
folderId
|
|
1161
|
+
id String @id @default(cuid())
|
|
1162
|
+
folderId String
|
|
1161
1163
|
|
|
1162
1164
|
// Share target (either user OR organization, enforced by application logic)
|
|
1163
1165
|
userId String?
|
|
1164
1166
|
organizationId String?
|
|
1165
1167
|
|
|
1166
1168
|
// Permission level
|
|
1167
|
-
permission
|
|
1169
|
+
permission FolderPermission @default(VIEW)
|
|
1168
1170
|
|
|
1169
1171
|
// Sharing metadata
|
|
1170
|
-
sharedById
|
|
1171
|
-
sharedAt
|
|
1172
|
-
expiresAt
|
|
1172
|
+
sharedById String
|
|
1173
|
+
sharedAt DateTime @default(now())
|
|
1174
|
+
expiresAt DateTime?
|
|
1173
1175
|
|
|
1174
1176
|
// Relations
|
|
1175
|
-
folder
|
|
1176
|
-
user
|
|
1177
|
-
organization
|
|
1178
|
-
sharedBy
|
|
1177
|
+
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
|
|
1178
|
+
user User? @relation("FolderShareUser", fields: [userId], references: [id], onDelete: Cascade)
|
|
1179
|
+
organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
|
1180
|
+
sharedBy User @relation("FolderShareCreator", fields: [sharedById], references: [id], onDelete: Restrict)
|
|
1179
1181
|
|
|
1180
1182
|
// Partial unique indexes for nullable columns
|
|
1181
1183
|
@@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;
|