@danielcok17/prisma-db 1.20.24 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielcok17/prisma-db",
3
- "version": "1.20.24",
3
+ "version": "1.20.25",
4
4
  "description": "Shared Prisma schema for Legal AI applications",
5
5
  "repository": {
6
6
  "type": "git",
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 @id @default(cuid())
291
+ id String @id @default(cuid())
292
292
  name String
293
293
  userId String
294
- isShareable Boolean @default(false)
295
- shareUrl String? @unique
294
+ isShareable Boolean @default(false)
295
+ shareUrl String? @unique
296
296
  sharedAt DateTime?
297
- createdAt DateTime @default(now())
298
- updatedAt DateTime @updatedAt
297
+ createdAt DateTime @default(now())
298
+ updatedAt DateTime @updatedAt
299
299
  summary String?
300
- messagesSinceLastSummary Int? @default(0)
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 @relation(fields: [userId], references: [id])
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 String @id @default(cuid())
349
- answerId String?
350
- fileName String
351
- fileType String
352
- base64Data String
353
- convertedBase64Data String?
354
- convertedFileName String?
355
- convertedFileType String?
356
- extractedText String?
357
- positionData Json? // Serialized TextPosition[] for coordinate-based citation highlighting
358
- uploadedAt DateTime @default(now())
359
- answer Answer? @relation(fields: [answerId], references: [id], onDelete: Cascade)
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 // nahraté cez UI priečinka / prípadu
943
- CHAT // nahraté z chatovej konverzácie
944
- STORAGE // nahraté priamo do "Môj archív"
945
- CANVAS // AI-vygenerovaný dokument uložený z canvas editora
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 {
@@ -955,12 +964,12 @@ enum IndexingJobStatus {
955
964
  // Paid AI operations on customer files (indexing + user-file search), tracked
956
965
  // for an admin-only cost overview.
957
966
  enum FileOperationType {
958
- OCR // text extraction from images / scanned PDFs
959
- EMBEDDING // vector embedding of summary cards at index time
960
- CARD_GEN // LLM summary-card generation
961
- SEARCH_EMBEDDING // query embedding during user-file search
962
- RERANK // reranker call during user-file search
963
- ANSWER // LLM answer over user files
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
964
973
  }
965
974
 
966
975
  enum FolderType {
@@ -979,13 +988,13 @@ enum FolderPermission {
979
988
  // Nový model pre logovanie admin akcií
980
989
  model AdminActionLog {
981
990
  id String @id @default(cuid())
982
- action String // APPROVE_USER, REJECT_USER, etc.
983
- targetUserId String // ID používateľa, na ktorého sa akcia vzťahuje
984
- adminEmail String // E-mail admina, ktorý vykonal akciu
985
- details Json? // Ďalšie detaily akcie (reason, notes, etc.)
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.)
986
995
  createdAt DateTime @default(now())
987
996
  // Correlation + security audit fields
988
- requestId String? // korelácia s Pino logom (AsyncLocalStorage requestId)
997
+ requestId String? // korelácia s Pino logom (AsyncLocalStorage requestId)
989
998
  ipAddress String?
990
999
  userAgent String?
991
1000
  brand String @default("smartlex")
@@ -1003,22 +1012,22 @@ model AdminActionLog {
1003
1012
  // GDPR Article 30 — data access audit trail
1004
1013
  // WHO accessed WHAT legal data, WHEN, and from WHERE
1005
1014
  model AuditLog {
1006
- id String @id @default(cuid())
1015
+ id String @id @default(cuid())
1007
1016
 
1008
1017
  userId String?
1009
1018
  adminEmail String?
1010
1019
 
1011
- action String // READ_CONVERSATION | DELETE_ANSWER | EXPORT_DATA | VIEW_USER | SHARE_FOLDER
1012
- entityType String // "Conversation" | "Answer" | "UserFile" | "User" | "Folder"
1020
+ action String // READ_CONVERSATION | DELETE_ANSWER | EXPORT_DATA | VIEW_USER | SHARE_FOLDER
1021
+ entityType String // "Conversation" | "Answer" | "UserFile" | "User" | "Folder"
1013
1022
  entityId String
1014
- brand String @default("smartlex")
1023
+ brand String @default("smartlex")
1015
1024
 
1016
- requestId String?
1017
- ipAddress String?
1018
- userAgent String?
1019
- metadata Json?
1025
+ requestId String?
1026
+ ipAddress String?
1027
+ userAgent String?
1028
+ metadata Json?
1020
1029
 
1021
- createdAt DateTime @default(now())
1030
+ createdAt DateTime @default(now())
1022
1031
 
1023
1032
  @@index([userId])
1024
1033
  @@index([action])
@@ -1031,7 +1040,7 @@ model AuditLog {
1031
1040
  // Daily aggregated usage — billing analytics, cost trending
1032
1041
  // Populated by nightly cron job from AnswerMetrics — never write from request path
1033
1042
  model UsageDaily {
1034
- id String @id @default(cuid())
1043
+ id String @id @default(cuid())
1035
1044
 
1036
1045
  date DateTime @db.Date
1037
1046
  userId String?
@@ -1040,16 +1049,16 @@ model UsageDaily {
1040
1049
  aiProvider String
1041
1050
  aiModel String
1042
1051
 
1043
- messageCount Int @default(0)
1044
- tokenCount Int @default(0)
1045
- totalCost Float @default(0)
1046
- aiCost Float @default(0)
1047
- ragCost Float @default(0)
1048
- avgDurationMs Float?
1049
- errorCount Int @default(0)
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)
1050
1059
 
1051
- createdAt DateTime @default(now())
1052
- updatedAt DateTime @updatedAt
1060
+ createdAt DateTime @default(now())
1061
+ updatedAt DateTime @updatedAt
1053
1062
 
1054
1063
  @@unique([date, userId, organizationId, aiModel])
1055
1064
  @@index([date])
@@ -1282,44 +1291,44 @@ enum IngestionStatus {
1282
1291
  }
1283
1292
 
1284
1293
  model UserFile {
1285
- id String @id @default(cuid())
1286
- userId String
1294
+ id String @id @default(cuid())
1295
+ userId String
1287
1296
 
1288
- storageKey String @unique
1289
- storageUrl String?
1290
- checksumSha256 String
1297
+ storageKey String @unique
1298
+ storageUrl String?
1299
+ checksumSha256 String
1291
1300
 
1292
- fileName String
1293
- fileType String
1294
- fileSize Int
1295
- pageCount Int?
1296
- language String? @default("sk")
1301
+ fileName String
1302
+ fileType String
1303
+ fileSize Int
1304
+ pageCount Int?
1305
+ language String? @default("sk")
1297
1306
 
1298
- status UserFileStatus @default(STORED)
1307
+ status UserFileStatus @default(STORED)
1299
1308
 
1300
- docType String?
1301
- legalArea String?
1302
- summary String?
1303
- extractedData Json?
1304
- references String[] @default([])
1305
- chunkCount Int?
1306
- qdrantDocId String? @unique
1307
- indexingCost Float?
1308
- indexedAt DateTime?
1309
- indexError String?
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?
1310
1319
 
1311
- tags String[] @default([])
1320
+ tags String[] @default([])
1312
1321
 
1313
- parentFileId String?
1314
- versionNumber Int @default(1)
1322
+ parentFileId String?
1323
+ versionNumber Int @default(1)
1315
1324
 
1316
1325
  uploadSource UserFileUploadSource @default(STORAGE)
1317
- uploadContextId String? // polymorphic: folderId | conversationId | documentId | null
1326
+ uploadContextId String? // polymorphic: folderId | conversationId | documentId | null
1318
1327
  isAiGenerated Boolean @default(false) // true = AI vygeneroval obsah (canvas export)
1319
1328
 
1320
- createdAt DateTime @default(now())
1321
- updatedAt DateTime @updatedAt
1322
- deletedAt DateTime?
1329
+ createdAt DateTime @default(now())
1330
+ updatedAt DateTime @updatedAt
1331
+ deletedAt DateTime?
1323
1332
 
1324
1333
  user User @relation("UserFiles", fields: [userId], references: [id], onDelete: Cascade)
1325
1334
  indexingJobs FileIndexingJob[]
@@ -1340,13 +1349,13 @@ model UserFile {
1340
1349
  }
1341
1350
 
1342
1351
  model UserFileConversation {
1343
- id String @id @default(cuid())
1352
+ id String @id @default(cuid())
1344
1353
  fileId String
1345
1354
  conversationId String
1346
- createdAt DateTime @default(now())
1355
+ createdAt DateTime @default(now())
1347
1356
 
1348
- file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1349
- conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
1357
+ file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1358
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
1350
1359
 
1351
1360
  @@unique([fileId, conversationId])
1352
1361
  @@index([fileId])
@@ -1354,16 +1363,16 @@ model UserFileConversation {
1354
1363
  }
1355
1364
 
1356
1365
  model UserFileSummaryCard {
1357
- id String @id @default(cuid())
1358
- fileId String
1359
- userId String
1366
+ id String @id @default(cuid())
1367
+ fileId String
1368
+ userId String
1360
1369
 
1361
- schemaVersion String @default("user_file_summary_card_v1")
1362
- cardId String
1363
- cardType String
1364
- title String
1365
- summary String
1366
- searchText String
1370
+ schemaVersion String @default("user_file_summary_card_v1")
1371
+ cardId String
1372
+ cardType String
1373
+ title String
1374
+ summary String
1375
+ searchText String
1367
1376
 
1368
1377
  normalizedEntities Json?
1369
1378
  sourceRefs Json
@@ -1372,11 +1381,11 @@ model UserFileSummaryCard {
1372
1381
  confidence Float?
1373
1382
  warnings Json?
1374
1383
 
1375
- qdrantPointId String @unique
1376
- createdAt DateTime @default(now())
1377
- updatedAt DateTime @updatedAt
1384
+ qdrantPointId String @unique
1385
+ createdAt DateTime @default(now())
1386
+ updatedAt DateTime @updatedAt
1378
1387
 
1379
- file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1388
+ file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1380
1389
 
1381
1390
  @@unique([fileId, cardId])
1382
1391
  @@index([fileId])
@@ -1396,7 +1405,7 @@ model FileIndexingJob {
1396
1405
  finishedAt DateTime?
1397
1406
  createdAt DateTime @default(now())
1398
1407
 
1399
- file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1408
+ file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1400
1409
 
1401
1410
  @@index([fileId])
1402
1411
  @@index([userId])
@@ -1407,26 +1416,26 @@ model FileIndexingJob {
1407
1416
  // Append-only ledger of paid AI operations on customer files. Scalar fileId/
1408
1417
  // userId (no relations) so cost records survive file/user deletion for accounting.
1409
1418
  model FileOperationCost {
1410
- id String @id @default(cuid())
1419
+ id String @id @default(cuid())
1411
1420
  userId String
1412
- fileId String? // null for search/answer ops not tied to one file
1413
- conversationId String? // set for SEARCH_EMBEDDING / RERANK / ANSWER
1421
+ fileId String? // null for search/answer ops not tied to one file
1422
+ conversationId String? // set for SEARCH_EMBEDDING / RERANK / ANSWER
1414
1423
 
1415
- operationType FileOperationType
1416
- provider String // openai | mistral | google | gemini | cohere
1417
- model String? // e.g. text-embedding-3-large, mistral-ocr-latest
1424
+ operationType FileOperationType
1425
+ provider String // openai | mistral | google | gemini | cohere
1426
+ model String? // e.g. text-embedding-3-large, mistral-ocr-latest
1418
1427
 
1419
- costUsd Float @default(0)
1420
- estimated Boolean @default(true) // false = actual usage from provider
1428
+ costUsd Float @default(0)
1429
+ estimated Boolean @default(true) // false = actual usage from provider
1421
1430
 
1422
- inputTokens Int?
1423
- outputTokens Int?
1424
- totalTokens Int?
1425
- pages Int? // OCR pages
1426
- units Int? // generic unit count (embedded docs, chars, etc.)
1427
- durationMs Int?
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?
1428
1437
 
1429
- createdAt DateTime @default(now())
1438
+ createdAt DateTime @default(now())
1430
1439
 
1431
1440
  @@index([userId])
1432
1441
  @@index([fileId])
@@ -1448,7 +1457,7 @@ model FileDeadline {
1448
1457
  sourcePage Int?
1449
1458
  createdAt DateTime @default(now())
1450
1459
 
1451
- file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1460
+ file UserFile @relation(fields: [fileId], references: [id], onDelete: Cascade)
1452
1461
 
1453
1462
  @@index([fileId])
1454
1463
  @@index([userId])
@@ -1469,7 +1478,7 @@ model FileReviewTable {
1469
1478
  createdAt DateTime @default(now())
1470
1479
  updatedAt DateTime @updatedAt
1471
1480
 
1472
- user User @relation("UserReviewTables", fields: [userId], references: [id], onDelete: Cascade)
1481
+ user User @relation("UserReviewTables", fields: [userId], references: [id], onDelete: Cascade)
1473
1482
 
1474
1483
  @@index([userId])
1475
1484
  @@index([folderId])
@@ -1783,7 +1792,7 @@ model AdminGrant {
1783
1792
  isActive Boolean @default(true)
1784
1793
  revokedAt DateTime?
1785
1794
  revokedBy String?
1786
- invoiceNumber String? // Číslo faktúry pri platenom grante (prevod/faktúra)
1795
+ invoiceNumber String? // Číslo faktúry pri platenom grante (prevod/faktúra)
1787
1796
  createdAt DateTime @default(now())
1788
1797
  updatedAt DateTime @updatedAt
1789
1798
 
@@ -2074,3 +2083,104 @@ model Referral {
2074
2083
  @@index([referralCode])
2075
2084
  @@index([status])
2076
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,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
+