@danielcok17/prisma-db 1.20.23 → 1.20.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/prisma/app.prisma
CHANGED
|
@@ -288,20 +288,20 @@ enum MemberRole {
|
|
|
288
288
|
|
|
289
289
|
// ZJEDNODUŠENÝ Conversation - len metadata, bez duplikátov
|
|
290
290
|
model Conversation {
|
|
291
|
-
id String
|
|
291
|
+
id String @id @default(cuid())
|
|
292
292
|
name String
|
|
293
293
|
userId String
|
|
294
|
-
isShareable Boolean
|
|
295
|
-
shareUrl String?
|
|
294
|
+
isShareable Boolean @default(false)
|
|
295
|
+
shareUrl String? @unique
|
|
296
296
|
sharedAt DateTime?
|
|
297
|
-
createdAt DateTime
|
|
298
|
-
updatedAt DateTime
|
|
297
|
+
createdAt DateTime @default(now())
|
|
298
|
+
updatedAt DateTime @updatedAt
|
|
299
299
|
summary String?
|
|
300
|
-
messagesSinceLastSummary Int?
|
|
300
|
+
messagesSinceLastSummary Int? @default(0)
|
|
301
301
|
integrityHash String? // HMAC-SHA256 hash chain - posledný chainHash pre verifikáciu integrity
|
|
302
302
|
// Relácie
|
|
303
303
|
answers Answer[]
|
|
304
|
-
user User
|
|
304
|
+
user User @relation(fields: [userId], references: [id])
|
|
305
305
|
workflowLogs WorkflowLog[]
|
|
306
306
|
canvasDocuments CanvasDocument[]
|
|
307
307
|
fileUsages UserFileConversation[]
|
|
@@ -337,6 +337,7 @@ model Answer {
|
|
|
337
337
|
WorkflowLog WorkflowLog[]
|
|
338
338
|
files MessageFile[]
|
|
339
339
|
canvasDocumentId String? // No FK constraint - flexible document management
|
|
340
|
+
|
|
340
341
|
@@index([conversationId])
|
|
341
342
|
@@index([messageId])
|
|
342
343
|
@@index([role])
|
|
@@ -345,18 +346,18 @@ model Answer {
|
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
model MessageFile {
|
|
348
|
-
id
|
|
349
|
-
answerId
|
|
350
|
-
fileName
|
|
351
|
-
fileType
|
|
352
|
-
base64Data
|
|
353
|
-
convertedBase64Data
|
|
354
|
-
convertedFileName
|
|
355
|
-
convertedFileType
|
|
356
|
-
extractedText
|
|
357
|
-
positionData
|
|
358
|
-
uploadedAt
|
|
359
|
-
answer
|
|
349
|
+
id String @id @default(cuid())
|
|
350
|
+
answerId String?
|
|
351
|
+
fileName String
|
|
352
|
+
fileType String
|
|
353
|
+
base64Data String
|
|
354
|
+
convertedBase64Data String?
|
|
355
|
+
convertedFileName String?
|
|
356
|
+
convertedFileType String?
|
|
357
|
+
extractedText String?
|
|
358
|
+
positionData Json? // Serialized TextPosition[] for coordinate-based citation highlighting
|
|
359
|
+
uploadedAt DateTime @default(now())
|
|
360
|
+
answer Answer? @relation(fields: [answerId], references: [id], onDelete: Cascade)
|
|
360
361
|
|
|
361
362
|
@@index([answerId])
|
|
362
363
|
@@index([fileType])
|
|
@@ -565,6 +566,14 @@ model TouchPoint {
|
|
|
565
566
|
country String? // "SK", "CZ", "US"
|
|
566
567
|
city String? // "Bratislava", "Prague"
|
|
567
568
|
|
|
569
|
+
// Ad-click identifiers (parsed today but never persisted — needed for offline
|
|
570
|
+
// conversion upload to Google/Meta/Microsoft/LinkedIn). All nullable/additive.
|
|
571
|
+
gclid String? // Google Ads click id
|
|
572
|
+
fbclid String? // Meta/Facebook click id
|
|
573
|
+
msclkid String? // Microsoft Ads click id
|
|
574
|
+
liFatId String? // LinkedIn click id
|
|
575
|
+
landingUrl String? // First landing URL of the session
|
|
576
|
+
|
|
568
577
|
// Timestamp
|
|
569
578
|
timestamp DateTime @default(now())
|
|
570
579
|
|
|
@@ -939,10 +948,10 @@ enum UserFileStatus {
|
|
|
939
948
|
}
|
|
940
949
|
|
|
941
950
|
enum UserFileUploadSource {
|
|
942
|
-
FOLDER
|
|
943
|
-
CHAT
|
|
944
|
-
STORAGE
|
|
945
|
-
CANVAS
|
|
951
|
+
FOLDER // nahraté cez UI priečinka / prípadu
|
|
952
|
+
CHAT // nahraté z chatovej konverzácie
|
|
953
|
+
STORAGE // nahraté priamo do "Môj archív"
|
|
954
|
+
CANVAS // AI-vygenerovaný dokument uložený z canvas editora
|
|
946
955
|
}
|
|
947
956
|
|
|
948
957
|
enum IndexingJobStatus {
|
|
@@ -952,6 +961,17 @@ enum IndexingJobStatus {
|
|
|
952
961
|
FAILED
|
|
953
962
|
}
|
|
954
963
|
|
|
964
|
+
// Paid AI operations on customer files (indexing + user-file search), tracked
|
|
965
|
+
// for an admin-only cost overview.
|
|
966
|
+
enum FileOperationType {
|
|
967
|
+
OCR // text extraction from images / scanned PDFs
|
|
968
|
+
EMBEDDING // vector embedding of summary cards at index time
|
|
969
|
+
CARD_GEN // LLM summary-card generation
|
|
970
|
+
SEARCH_EMBEDDING // query embedding during user-file search
|
|
971
|
+
RERANK // reranker call during user-file search
|
|
972
|
+
ANSWER // LLM answer over user files
|
|
973
|
+
}
|
|
974
|
+
|
|
955
975
|
enum FolderType {
|
|
956
976
|
PERSONAL
|
|
957
977
|
MATTER
|
|
@@ -968,13 +988,13 @@ enum FolderPermission {
|
|
|
968
988
|
// Nový model pre logovanie admin akcií
|
|
969
989
|
model AdminActionLog {
|
|
970
990
|
id String @id @default(cuid())
|
|
971
|
-
action String
|
|
972
|
-
targetUserId String
|
|
973
|
-
adminEmail String
|
|
974
|
-
details Json?
|
|
991
|
+
action String // APPROVE_USER, REJECT_USER, etc.
|
|
992
|
+
targetUserId String // ID používateľa, na ktorého sa akcia vzťahuje
|
|
993
|
+
adminEmail String // E-mail admina, ktorý vykonal akciu
|
|
994
|
+
details Json? // Ďalšie detaily akcie (reason, notes, etc.)
|
|
975
995
|
createdAt DateTime @default(now())
|
|
976
996
|
// Correlation + security audit fields
|
|
977
|
-
requestId String?
|
|
997
|
+
requestId String? // korelácia s Pino logom (AsyncLocalStorage requestId)
|
|
978
998
|
ipAddress String?
|
|
979
999
|
userAgent String?
|
|
980
1000
|
brand String @default("smartlex")
|
|
@@ -992,22 +1012,22 @@ model AdminActionLog {
|
|
|
992
1012
|
// GDPR Article 30 — data access audit trail
|
|
993
1013
|
// WHO accessed WHAT legal data, WHEN, and from WHERE
|
|
994
1014
|
model AuditLog {
|
|
995
|
-
id
|
|
1015
|
+
id String @id @default(cuid())
|
|
996
1016
|
|
|
997
1017
|
userId String?
|
|
998
1018
|
adminEmail String?
|
|
999
1019
|
|
|
1000
|
-
action String
|
|
1001
|
-
entityType String
|
|
1020
|
+
action String // READ_CONVERSATION | DELETE_ANSWER | EXPORT_DATA | VIEW_USER | SHARE_FOLDER
|
|
1021
|
+
entityType String // "Conversation" | "Answer" | "UserFile" | "User" | "Folder"
|
|
1002
1022
|
entityId String
|
|
1003
|
-
brand String
|
|
1023
|
+
brand String @default("smartlex")
|
|
1004
1024
|
|
|
1005
|
-
requestId
|
|
1006
|
-
ipAddress
|
|
1007
|
-
userAgent
|
|
1008
|
-
metadata
|
|
1025
|
+
requestId String?
|
|
1026
|
+
ipAddress String?
|
|
1027
|
+
userAgent String?
|
|
1028
|
+
metadata Json?
|
|
1009
1029
|
|
|
1010
|
-
createdAt
|
|
1030
|
+
createdAt DateTime @default(now())
|
|
1011
1031
|
|
|
1012
1032
|
@@index([userId])
|
|
1013
1033
|
@@index([action])
|
|
@@ -1020,7 +1040,7 @@ model AuditLog {
|
|
|
1020
1040
|
// Daily aggregated usage — billing analytics, cost trending
|
|
1021
1041
|
// Populated by nightly cron job from AnswerMetrics — never write from request path
|
|
1022
1042
|
model UsageDaily {
|
|
1023
|
-
id
|
|
1043
|
+
id String @id @default(cuid())
|
|
1024
1044
|
|
|
1025
1045
|
date DateTime @db.Date
|
|
1026
1046
|
userId String?
|
|
@@ -1029,16 +1049,16 @@ model UsageDaily {
|
|
|
1029
1049
|
aiProvider String
|
|
1030
1050
|
aiModel String
|
|
1031
1051
|
|
|
1032
|
-
messageCount
|
|
1033
|
-
tokenCount
|
|
1034
|
-
totalCost
|
|
1035
|
-
aiCost
|
|
1036
|
-
ragCost
|
|
1037
|
-
avgDurationMs
|
|
1038
|
-
errorCount
|
|
1052
|
+
messageCount Int @default(0)
|
|
1053
|
+
tokenCount Int @default(0)
|
|
1054
|
+
totalCost Float @default(0)
|
|
1055
|
+
aiCost Float @default(0)
|
|
1056
|
+
ragCost Float @default(0)
|
|
1057
|
+
avgDurationMs Float?
|
|
1058
|
+
errorCount Int @default(0)
|
|
1039
1059
|
|
|
1040
|
-
createdAt
|
|
1041
|
-
updatedAt
|
|
1060
|
+
createdAt DateTime @default(now())
|
|
1061
|
+
updatedAt DateTime @updatedAt
|
|
1042
1062
|
|
|
1043
1063
|
@@unique([date, userId, organizationId, aiModel])
|
|
1044
1064
|
@@index([date])
|
|
@@ -1271,44 +1291,44 @@ enum IngestionStatus {
|
|
|
1271
1291
|
}
|
|
1272
1292
|
|
|
1273
1293
|
model UserFile {
|
|
1274
|
-
id
|
|
1275
|
-
userId
|
|
1294
|
+
id String @id @default(cuid())
|
|
1295
|
+
userId String
|
|
1276
1296
|
|
|
1277
|
-
storageKey
|
|
1278
|
-
storageUrl
|
|
1279
|
-
checksumSha256
|
|
1297
|
+
storageKey String @unique
|
|
1298
|
+
storageUrl String?
|
|
1299
|
+
checksumSha256 String
|
|
1280
1300
|
|
|
1281
|
-
fileName
|
|
1282
|
-
fileType
|
|
1283
|
-
fileSize
|
|
1284
|
-
pageCount
|
|
1285
|
-
language
|
|
1301
|
+
fileName String
|
|
1302
|
+
fileType String
|
|
1303
|
+
fileSize Int
|
|
1304
|
+
pageCount Int?
|
|
1305
|
+
language String? @default("sk")
|
|
1286
1306
|
|
|
1287
|
-
status
|
|
1307
|
+
status UserFileStatus @default(STORED)
|
|
1288
1308
|
|
|
1289
|
-
docType
|
|
1290
|
-
legalArea
|
|
1291
|
-
summary
|
|
1292
|
-
extractedData
|
|
1293
|
-
references
|
|
1294
|
-
chunkCount
|
|
1295
|
-
qdrantDocId
|
|
1296
|
-
indexingCost
|
|
1297
|
-
indexedAt
|
|
1298
|
-
indexError
|
|
1309
|
+
docType String?
|
|
1310
|
+
legalArea String?
|
|
1311
|
+
summary String?
|
|
1312
|
+
extractedData Json?
|
|
1313
|
+
references String[] @default([])
|
|
1314
|
+
chunkCount Int?
|
|
1315
|
+
qdrantDocId String? @unique
|
|
1316
|
+
indexingCost Float?
|
|
1317
|
+
indexedAt DateTime?
|
|
1318
|
+
indexError String?
|
|
1299
1319
|
|
|
1300
|
-
tags
|
|
1320
|
+
tags String[] @default([])
|
|
1301
1321
|
|
|
1302
|
-
parentFileId
|
|
1303
|
-
versionNumber
|
|
1322
|
+
parentFileId String?
|
|
1323
|
+
versionNumber Int @default(1)
|
|
1304
1324
|
|
|
1305
1325
|
uploadSource UserFileUploadSource @default(STORAGE)
|
|
1306
|
-
uploadContextId String?
|
|
1326
|
+
uploadContextId String? // polymorphic: folderId | conversationId | documentId | null
|
|
1307
1327
|
isAiGenerated Boolean @default(false) // true = AI vygeneroval obsah (canvas export)
|
|
1308
1328
|
|
|
1309
|
-
createdAt
|
|
1310
|
-
updatedAt
|
|
1311
|
-
deletedAt
|
|
1329
|
+
createdAt DateTime @default(now())
|
|
1330
|
+
updatedAt DateTime @updatedAt
|
|
1331
|
+
deletedAt DateTime?
|
|
1312
1332
|
|
|
1313
1333
|
user User @relation("UserFiles", fields: [userId], references: [id], onDelete: Cascade)
|
|
1314
1334
|
indexingJobs FileIndexingJob[]
|
|
@@ -1329,13 +1349,13 @@ model UserFile {
|
|
|
1329
1349
|
}
|
|
1330
1350
|
|
|
1331
1351
|
model UserFileConversation {
|
|
1332
|
-
id String
|
|
1352
|
+
id String @id @default(cuid())
|
|
1333
1353
|
fileId String
|
|
1334
1354
|
conversationId String
|
|
1335
|
-
createdAt DateTime
|
|
1355
|
+
createdAt DateTime @default(now())
|
|
1336
1356
|
|
|
1337
|
-
file
|
|
1338
|
-
conversation
|
|
1357
|
+
file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
|
1358
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
1339
1359
|
|
|
1340
1360
|
@@unique([fileId, conversationId])
|
|
1341
1361
|
@@index([fileId])
|
|
@@ -1343,16 +1363,16 @@ model UserFileConversation {
|
|
|
1343
1363
|
}
|
|
1344
1364
|
|
|
1345
1365
|
model UserFileSummaryCard {
|
|
1346
|
-
id
|
|
1347
|
-
fileId
|
|
1348
|
-
userId
|
|
1366
|
+
id String @id @default(cuid())
|
|
1367
|
+
fileId String
|
|
1368
|
+
userId String
|
|
1349
1369
|
|
|
1350
|
-
schemaVersion
|
|
1351
|
-
cardId
|
|
1352
|
-
cardType
|
|
1353
|
-
title
|
|
1354
|
-
summary
|
|
1355
|
-
searchText
|
|
1370
|
+
schemaVersion String @default("user_file_summary_card_v1")
|
|
1371
|
+
cardId String
|
|
1372
|
+
cardType String
|
|
1373
|
+
title String
|
|
1374
|
+
summary String
|
|
1375
|
+
searchText String
|
|
1356
1376
|
|
|
1357
1377
|
normalizedEntities Json?
|
|
1358
1378
|
sourceRefs Json
|
|
@@ -1361,11 +1381,11 @@ model UserFileSummaryCard {
|
|
|
1361
1381
|
confidence Float?
|
|
1362
1382
|
warnings Json?
|
|
1363
1383
|
|
|
1364
|
-
qdrantPointId
|
|
1365
|
-
createdAt
|
|
1366
|
-
updatedAt
|
|
1384
|
+
qdrantPointId String @unique
|
|
1385
|
+
createdAt DateTime @default(now())
|
|
1386
|
+
updatedAt DateTime @updatedAt
|
|
1367
1387
|
|
|
1368
|
-
file
|
|
1388
|
+
file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
|
1369
1389
|
|
|
1370
1390
|
@@unique([fileId, cardId])
|
|
1371
1391
|
@@index([fileId])
|
|
@@ -1385,7 +1405,7 @@ model FileIndexingJob {
|
|
|
1385
1405
|
finishedAt DateTime?
|
|
1386
1406
|
createdAt DateTime @default(now())
|
|
1387
1407
|
|
|
1388
|
-
file
|
|
1408
|
+
file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
|
1389
1409
|
|
|
1390
1410
|
@@index([fileId])
|
|
1391
1411
|
@@index([userId])
|
|
@@ -1393,6 +1413,37 @@ model FileIndexingJob {
|
|
|
1393
1413
|
@@index([createdAt])
|
|
1394
1414
|
}
|
|
1395
1415
|
|
|
1416
|
+
// Append-only ledger of paid AI operations on customer files. Scalar fileId/
|
|
1417
|
+
// userId (no relations) so cost records survive file/user deletion for accounting.
|
|
1418
|
+
model FileOperationCost {
|
|
1419
|
+
id String @id @default(cuid())
|
|
1420
|
+
userId String
|
|
1421
|
+
fileId String? // null for search/answer ops not tied to one file
|
|
1422
|
+
conversationId String? // set for SEARCH_EMBEDDING / RERANK / ANSWER
|
|
1423
|
+
|
|
1424
|
+
operationType FileOperationType
|
|
1425
|
+
provider String // openai | mistral | google | gemini | cohere
|
|
1426
|
+
model String? // e.g. text-embedding-3-large, mistral-ocr-latest
|
|
1427
|
+
|
|
1428
|
+
costUsd Float @default(0)
|
|
1429
|
+
estimated Boolean @default(true) // false = actual usage from provider
|
|
1430
|
+
|
|
1431
|
+
inputTokens Int?
|
|
1432
|
+
outputTokens Int?
|
|
1433
|
+
totalTokens Int?
|
|
1434
|
+
pages Int? // OCR pages
|
|
1435
|
+
units Int? // generic unit count (embedded docs, chars, etc.)
|
|
1436
|
+
durationMs Int?
|
|
1437
|
+
|
|
1438
|
+
createdAt DateTime @default(now())
|
|
1439
|
+
|
|
1440
|
+
@@index([userId])
|
|
1441
|
+
@@index([fileId])
|
|
1442
|
+
@@index([operationType])
|
|
1443
|
+
@@index([createdAt])
|
|
1444
|
+
@@index([userId, createdAt])
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1396
1447
|
model FileDeadline {
|
|
1397
1448
|
id String @id @default(cuid())
|
|
1398
1449
|
fileId String
|
|
@@ -1406,7 +1457,7 @@ model FileDeadline {
|
|
|
1406
1457
|
sourcePage Int?
|
|
1407
1458
|
createdAt DateTime @default(now())
|
|
1408
1459
|
|
|
1409
|
-
file
|
|
1460
|
+
file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
|
|
1410
1461
|
|
|
1411
1462
|
@@index([fileId])
|
|
1412
1463
|
@@index([userId])
|
|
@@ -1427,7 +1478,7 @@ model FileReviewTable {
|
|
|
1427
1478
|
createdAt DateTime @default(now())
|
|
1428
1479
|
updatedAt DateTime @updatedAt
|
|
1429
1480
|
|
|
1430
|
-
user
|
|
1481
|
+
user User @relation("UserReviewTables", fields: [userId], references: [id], onDelete: Cascade)
|
|
1431
1482
|
|
|
1432
1483
|
@@index([userId])
|
|
1433
1484
|
@@index([folderId])
|
|
@@ -1741,7 +1792,7 @@ model AdminGrant {
|
|
|
1741
1792
|
isActive Boolean @default(true)
|
|
1742
1793
|
revokedAt DateTime?
|
|
1743
1794
|
revokedBy String?
|
|
1744
|
-
invoiceNumber String?
|
|
1795
|
+
invoiceNumber String? // Číslo faktúry pri platenom grante (prevod/faktúra)
|
|
1745
1796
|
createdAt DateTime @default(now())
|
|
1746
1797
|
updatedAt DateTime @updatedAt
|
|
1747
1798
|
|
|
@@ -2032,3 +2083,104 @@ model Referral {
|
|
|
2032
2083
|
@@index([referralCode])
|
|
2033
2084
|
@@index([status])
|
|
2034
2085
|
}
|
|
2086
|
+
|
|
2087
|
+
// ============================================
|
|
2088
|
+
// OUTBOUND ATTRIBUTION (SDR cold calls + Instantly cold email)
|
|
2089
|
+
// See consumer repo: OUTBOUND_RESEARCH_AND_AUDIT.md §5
|
|
2090
|
+
// ============================================
|
|
2091
|
+
|
|
2092
|
+
enum OutboundChannel {
|
|
2093
|
+
CALL
|
|
2094
|
+
EMAIL
|
|
2095
|
+
LINKEDIN
|
|
2096
|
+
EVENT
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/// A sales rep (SDR) — the attribution dimension previously missing everywhere.
|
|
2100
|
+
model Sdr {
|
|
2101
|
+
id String @id @default(cuid())
|
|
2102
|
+
userId String? @unique // if the SDR also has an app login
|
|
2103
|
+
name String
|
|
2104
|
+
slug String @unique // powers smartlex.sk/<slug> vanity links
|
|
2105
|
+
active Boolean @default(true)
|
|
2106
|
+
createdAt DateTime @default(now())
|
|
2107
|
+
|
|
2108
|
+
codes InviteCode[]
|
|
2109
|
+
touches OutboundTouch[]
|
|
2110
|
+
|
|
2111
|
+
@@index([active])
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/// One row per deliberate outbound contact: a call, or an Instantly send.
|
|
2115
|
+
model OutboundTouch {
|
|
2116
|
+
id String @id @default(cuid())
|
|
2117
|
+
channel OutboundChannel
|
|
2118
|
+
sdrId String?
|
|
2119
|
+
sdr Sdr? @relation(fields: [sdrId], references: [id], onDelete: SetNull)
|
|
2120
|
+
leadId String? // prospect record id (Instantly lead id, etc.)
|
|
2121
|
+
campaignId String? // Instantly campaign UUID
|
|
2122
|
+
instantlyStep Int?
|
|
2123
|
+
emailHash String? // sha256(normalized email) — pseudonymised, NOT anonymous (ROPA/DSAR)
|
|
2124
|
+
occurredAt DateTime
|
|
2125
|
+
outcome String? // 'connected' | 'voicemail' | 'reply_positive' | ...
|
|
2126
|
+
inviteCodeId String?
|
|
2127
|
+
createdAt DateTime @default(now())
|
|
2128
|
+
|
|
2129
|
+
@@index([sdrId, occurredAt])
|
|
2130
|
+
@@index([campaignId])
|
|
2131
|
+
@@index([emailHash])
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
/// Unguessable, phone-readable invite code (Crockford Base32). See consumer lib/invite-codes.ts.
|
|
2135
|
+
model InviteCode {
|
|
2136
|
+
id String @id @default(cuid())
|
|
2137
|
+
code String @unique // NORMALIZED lookup key (uppercase, no dashes, I/L->1, O->0)
|
|
2138
|
+
display String // "ABCD-EFGH-JKMN-P" as shown to humans
|
|
2139
|
+
campaignId String
|
|
2140
|
+
issuedBySdrId String // the attribution AppInvite.invitedByEmail cannot express
|
|
2141
|
+
sdr Sdr @relation(fields: [issuedBySdrId], references: [id], onDelete: Cascade)
|
|
2142
|
+
issuedToEmail String? // per-prospect; null for shared codes
|
|
2143
|
+
grantTier SubscriptionTier
|
|
2144
|
+
grantDays Int
|
|
2145
|
+
maxRedemptions Int @default(1)
|
|
2146
|
+
usedCount Int @default(0)
|
|
2147
|
+
clickCount Int @default(0)
|
|
2148
|
+
expiresAt DateTime?
|
|
2149
|
+
revokedAt DateTime?
|
|
2150
|
+
createdAt DateTime @default(now())
|
|
2151
|
+
|
|
2152
|
+
redemptions Redemption[]
|
|
2153
|
+
|
|
2154
|
+
@@index([issuedBySdrId])
|
|
2155
|
+
@@index([issuedToEmail])
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
/// One redemption. The unique constraints are the hard backstop against races.
|
|
2159
|
+
model Redemption {
|
|
2160
|
+
id String @id @default(cuid())
|
|
2161
|
+
codeId String
|
|
2162
|
+
code InviteCode @relation(fields: [codeId], references: [id], onDelete: Cascade)
|
|
2163
|
+
userId String
|
|
2164
|
+
emailDomain String
|
|
2165
|
+
sdrId String // denormalized: survives code reassignment
|
|
2166
|
+
campaignId String
|
|
2167
|
+
idempotencyKey String? @unique
|
|
2168
|
+
redeemedAt DateTime @default(now())
|
|
2169
|
+
|
|
2170
|
+
@@unique([codeId, userId]) // hard backstop: a user redeems a given code at most once
|
|
2171
|
+
@@unique([campaignId, emailDomain]) // one grant per company domain
|
|
2172
|
+
@@index([sdrId, redeemedAt])
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
/// One row per attribution SIGNAL (not a verdict) — let reporting pick the threshold.
|
|
2176
|
+
model LeadAttribution {
|
|
2177
|
+
id String @id @default(cuid())
|
|
2178
|
+
userId String
|
|
2179
|
+
leadId String?
|
|
2180
|
+
matchMethod String // 'invite_code'|'email_exact'|'domain'|'fuzzy_name'|'self_report'
|
|
2181
|
+
confidence Decimal @db.Decimal(3, 2)
|
|
2182
|
+
evidence Json @default("{}")
|
|
2183
|
+
createdAt DateTime @default(now())
|
|
2184
|
+
|
|
2185
|
+
@@index([userId])
|
|
2186
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "FileOperationType" AS ENUM ('OCR', 'EMBEDDING', 'CARD_GEN', 'SEARCH_EMBEDDING', 'RERANK', 'ANSWER');
|
|
3
|
+
|
|
4
|
+
-- CreateTable
|
|
5
|
+
CREATE TABLE "FileOperationCost" (
|
|
6
|
+
"id" TEXT NOT NULL,
|
|
7
|
+
"userId" TEXT NOT NULL,
|
|
8
|
+
"fileId" TEXT,
|
|
9
|
+
"conversationId" TEXT,
|
|
10
|
+
"operationType" "FileOperationType" NOT NULL,
|
|
11
|
+
"provider" TEXT NOT NULL,
|
|
12
|
+
"model" TEXT,
|
|
13
|
+
"costUsd" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
14
|
+
"estimated" BOOLEAN NOT NULL DEFAULT true,
|
|
15
|
+
"inputTokens" INTEGER,
|
|
16
|
+
"outputTokens" INTEGER,
|
|
17
|
+
"totalTokens" INTEGER,
|
|
18
|
+
"pages" INTEGER,
|
|
19
|
+
"units" INTEGER,
|
|
20
|
+
"durationMs" INTEGER,
|
|
21
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
22
|
+
|
|
23
|
+
CONSTRAINT "FileOperationCost_pkey" PRIMARY KEY ("id")
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- CreateIndex
|
|
27
|
+
CREATE INDEX "FileOperationCost_userId_idx" ON "FileOperationCost"("userId");
|
|
28
|
+
|
|
29
|
+
-- CreateIndex
|
|
30
|
+
CREATE INDEX "FileOperationCost_fileId_idx" ON "FileOperationCost"("fileId");
|
|
31
|
+
|
|
32
|
+
-- CreateIndex
|
|
33
|
+
CREATE INDEX "FileOperationCost_operationType_idx" ON "FileOperationCost"("operationType");
|
|
34
|
+
|
|
35
|
+
-- CreateIndex
|
|
36
|
+
CREATE INDEX "FileOperationCost_createdAt_idx" ON "FileOperationCost"("createdAt");
|
|
37
|
+
|
|
38
|
+
-- CreateIndex
|
|
39
|
+
CREATE INDEX "FileOperationCost_userId_createdAt_idx" ON "FileOperationCost"("userId", "createdAt");
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "OutboundChannel" AS ENUM ('CALL', 'EMAIL', 'LINKEDIN', 'EVENT');
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE "touch_points" ADD COLUMN "fbclid" TEXT,
|
|
6
|
+
ADD COLUMN "gclid" TEXT,
|
|
7
|
+
ADD COLUMN "landingUrl" TEXT,
|
|
8
|
+
ADD COLUMN "liFatId" TEXT,
|
|
9
|
+
ADD COLUMN "msclkid" TEXT;
|
|
10
|
+
|
|
11
|
+
-- CreateTable
|
|
12
|
+
CREATE TABLE "Sdr" (
|
|
13
|
+
"id" TEXT NOT NULL,
|
|
14
|
+
"userId" TEXT,
|
|
15
|
+
"name" TEXT NOT NULL,
|
|
16
|
+
"slug" TEXT NOT NULL,
|
|
17
|
+
"active" BOOLEAN NOT NULL DEFAULT true,
|
|
18
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
19
|
+
|
|
20
|
+
CONSTRAINT "Sdr_pkey" PRIMARY KEY ("id")
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
-- CreateTable
|
|
24
|
+
CREATE TABLE "OutboundTouch" (
|
|
25
|
+
"id" TEXT NOT NULL,
|
|
26
|
+
"channel" "OutboundChannel" NOT NULL,
|
|
27
|
+
"sdrId" TEXT,
|
|
28
|
+
"leadId" TEXT,
|
|
29
|
+
"campaignId" TEXT,
|
|
30
|
+
"instantlyStep" INTEGER,
|
|
31
|
+
"emailHash" TEXT,
|
|
32
|
+
"occurredAt" TIMESTAMP(3) NOT NULL,
|
|
33
|
+
"outcome" TEXT,
|
|
34
|
+
"inviteCodeId" TEXT,
|
|
35
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
36
|
+
|
|
37
|
+
CONSTRAINT "OutboundTouch_pkey" PRIMARY KEY ("id")
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- CreateTable
|
|
41
|
+
CREATE TABLE "InviteCode" (
|
|
42
|
+
"id" TEXT NOT NULL,
|
|
43
|
+
"code" TEXT NOT NULL,
|
|
44
|
+
"display" TEXT NOT NULL,
|
|
45
|
+
"campaignId" TEXT NOT NULL,
|
|
46
|
+
"issuedBySdrId" TEXT NOT NULL,
|
|
47
|
+
"issuedToEmail" TEXT,
|
|
48
|
+
"grantTier" "SubscriptionTier" NOT NULL,
|
|
49
|
+
"grantDays" INTEGER NOT NULL,
|
|
50
|
+
"maxRedemptions" INTEGER NOT NULL DEFAULT 1,
|
|
51
|
+
"usedCount" INTEGER NOT NULL DEFAULT 0,
|
|
52
|
+
"clickCount" INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
"expiresAt" TIMESTAMP(3),
|
|
54
|
+
"revokedAt" TIMESTAMP(3),
|
|
55
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
56
|
+
|
|
57
|
+
CONSTRAINT "InviteCode_pkey" PRIMARY KEY ("id")
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
-- CreateTable
|
|
61
|
+
CREATE TABLE "Redemption" (
|
|
62
|
+
"id" TEXT NOT NULL,
|
|
63
|
+
"codeId" TEXT NOT NULL,
|
|
64
|
+
"userId" TEXT NOT NULL,
|
|
65
|
+
"emailDomain" TEXT NOT NULL,
|
|
66
|
+
"sdrId" TEXT NOT NULL,
|
|
67
|
+
"campaignId" TEXT NOT NULL,
|
|
68
|
+
"idempotencyKey" TEXT,
|
|
69
|
+
"redeemedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
70
|
+
|
|
71
|
+
CONSTRAINT "Redemption_pkey" PRIMARY KEY ("id")
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
-- CreateTable
|
|
75
|
+
CREATE TABLE "LeadAttribution" (
|
|
76
|
+
"id" TEXT NOT NULL,
|
|
77
|
+
"userId" TEXT NOT NULL,
|
|
78
|
+
"leadId" TEXT,
|
|
79
|
+
"matchMethod" TEXT NOT NULL,
|
|
80
|
+
"confidence" DECIMAL(3,2) NOT NULL,
|
|
81
|
+
"evidence" JSONB NOT NULL DEFAULT '{}',
|
|
82
|
+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
|
|
84
|
+
CONSTRAINT "LeadAttribution_pkey" PRIMARY KEY ("id")
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
-- CreateIndex
|
|
88
|
+
CREATE UNIQUE INDEX "Sdr_userId_key" ON "Sdr"("userId");
|
|
89
|
+
|
|
90
|
+
-- CreateIndex
|
|
91
|
+
CREATE UNIQUE INDEX "Sdr_slug_key" ON "Sdr"("slug");
|
|
92
|
+
|
|
93
|
+
-- CreateIndex
|
|
94
|
+
CREATE INDEX "Sdr_active_idx" ON "Sdr"("active");
|
|
95
|
+
|
|
96
|
+
-- CreateIndex
|
|
97
|
+
CREATE INDEX "OutboundTouch_sdrId_occurredAt_idx" ON "OutboundTouch"("sdrId", "occurredAt");
|
|
98
|
+
|
|
99
|
+
-- CreateIndex
|
|
100
|
+
CREATE INDEX "OutboundTouch_campaignId_idx" ON "OutboundTouch"("campaignId");
|
|
101
|
+
|
|
102
|
+
-- CreateIndex
|
|
103
|
+
CREATE INDEX "OutboundTouch_emailHash_idx" ON "OutboundTouch"("emailHash");
|
|
104
|
+
|
|
105
|
+
-- CreateIndex
|
|
106
|
+
CREATE UNIQUE INDEX "InviteCode_code_key" ON "InviteCode"("code");
|
|
107
|
+
|
|
108
|
+
-- CreateIndex
|
|
109
|
+
CREATE INDEX "InviteCode_issuedBySdrId_idx" ON "InviteCode"("issuedBySdrId");
|
|
110
|
+
|
|
111
|
+
-- CreateIndex
|
|
112
|
+
CREATE INDEX "InviteCode_issuedToEmail_idx" ON "InviteCode"("issuedToEmail");
|
|
113
|
+
|
|
114
|
+
-- CreateIndex
|
|
115
|
+
CREATE UNIQUE INDEX "Redemption_idempotencyKey_key" ON "Redemption"("idempotencyKey");
|
|
116
|
+
|
|
117
|
+
-- CreateIndex
|
|
118
|
+
CREATE INDEX "Redemption_sdrId_redeemedAt_idx" ON "Redemption"("sdrId", "redeemedAt");
|
|
119
|
+
|
|
120
|
+
-- CreateIndex
|
|
121
|
+
CREATE UNIQUE INDEX "Redemption_codeId_userId_key" ON "Redemption"("codeId", "userId");
|
|
122
|
+
|
|
123
|
+
-- CreateIndex
|
|
124
|
+
CREATE UNIQUE INDEX "Redemption_campaignId_emailDomain_key" ON "Redemption"("campaignId", "emailDomain");
|
|
125
|
+
|
|
126
|
+
-- CreateIndex
|
|
127
|
+
CREATE INDEX "LeadAttribution_userId_idx" ON "LeadAttribution"("userId");
|
|
128
|
+
|
|
129
|
+
-- AddForeignKey
|
|
130
|
+
ALTER TABLE "OutboundTouch" ADD CONSTRAINT "OutboundTouch_sdrId_fkey" FOREIGN KEY ("sdrId") REFERENCES "Sdr"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
|
131
|
+
|
|
132
|
+
-- AddForeignKey
|
|
133
|
+
ALTER TABLE "InviteCode" ADD CONSTRAINT "InviteCode_issuedBySdrId_fkey" FOREIGN KEY ("issuedBySdrId") REFERENCES "Sdr"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
134
|
+
|
|
135
|
+
-- AddForeignKey
|
|
136
|
+
ALTER TABLE "Redemption" ADD CONSTRAINT "Redemption_codeId_fkey" FOREIGN KEY ("codeId") REFERENCES "InviteCode"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
137
|
+
|