@danielcok17/prisma-db 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielcok17/prisma-db",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "Shared Prisma schema for Legal AI applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/prisma/app.prisma CHANGED
@@ -88,6 +88,18 @@ model User {
88
88
  phoneVerified DateTime? // Kedy bol telefón overený cez OTP
89
89
  occupation String? // Povolanie (Právnik, Účtovník, etc.)
90
90
  preferredLanguage String? @default("sk") // Jazyk odpovedi (sk, cs, en)
91
+ // Email Lifecycle — 360° user view
92
+ lastActiveAt DateTime?
93
+ lastLoginAt DateTime?
94
+ activatedAt DateTime?
95
+ lifecycleStage LifecycleStage @default(REGISTERED)
96
+ lifetimeValue Int @default(0)
97
+ lastEmailSentAt DateTime?
98
+ lastMarketingEmailSentAt DateTime?
99
+ isEmailSuppressed Boolean @default(false)
100
+ referralCode String? @unique
101
+ churnRiskScore Int @default(0)
102
+ churnRiskUpdatedAt DateTime?
91
103
  // Relations
92
104
  approvalRequest UserApprovalRequest?
93
105
  stripeCustomer StripeCustomer? // ✨ B2C Stripe customer
@@ -118,6 +130,12 @@ model User {
118
130
  // Ingestion pipeline
119
131
  ingestedDocuments IngestedDocument[]
120
132
  adminGrants AdminGrant[]
133
+ emailSends EmailSend[]
134
+ emailConsent EmailConsent?
135
+ userScore UserScore?
136
+ sequenceEnrollments EmailSequenceEnrollment[]
137
+ referralsSent Referral[] @relation("ReferralsSent")
138
+ referralsReceived Referral[] @relation("ReferralsReceived")
121
139
 
122
140
  // Multi-brand: compound unique allows same email across brands
123
141
  @@unique([email, brand])
@@ -133,6 +151,9 @@ model User {
133
151
  @@index([registrationUtmSource])
134
152
  @@index([registrationUtmCampaign])
135
153
  @@index([firstVisitAt])
154
+ @@index([lifecycleStage])
155
+ @@index([lastActiveAt])
156
+ @@index([isEmailSuppressed])
136
157
  }
137
158
 
138
159
  // Nový model pre žiadosti o schválenie
@@ -1065,6 +1086,70 @@ enum IngestionStatus {
1065
1086
  ERROR
1066
1087
  }
1067
1088
 
1089
+ enum LifecycleStage {
1090
+ REGISTERED
1091
+ ACTIVATED
1092
+ PAYING
1093
+ SUSPENDED
1094
+ CHURNED
1095
+ REACTIVATED
1096
+ }
1097
+
1098
+ enum EmailStatus {
1099
+ QUEUED
1100
+ SENT
1101
+ DELIVERED
1102
+ BOUNCED
1103
+ FAILED
1104
+ COMPLAINED
1105
+ }
1106
+
1107
+ enum EnrollmentStatus {
1108
+ ACTIVE
1109
+ COMPLETED
1110
+ EXITED
1111
+ PAUSED
1112
+ }
1113
+
1114
+ enum LeadSegment {
1115
+ COLD
1116
+ WARM
1117
+ HOT
1118
+ CONVERTING
1119
+ }
1120
+
1121
+ enum ChurnRisk {
1122
+ NONE
1123
+ LOW
1124
+ MEDIUM
1125
+ HIGH
1126
+ }
1127
+
1128
+ enum BounceType {
1129
+ HARD
1130
+ SOFT
1131
+ }
1132
+
1133
+ enum ConsentAction {
1134
+ GRANTED
1135
+ WITHDRAWN
1136
+ MODIFIED
1137
+ }
1138
+
1139
+ enum SuppressionReason {
1140
+ HARD_BOUNCE
1141
+ COMPLAINT
1142
+ MANUAL
1143
+ SUNSET
1144
+ }
1145
+
1146
+ enum ReferralStatus {
1147
+ PENDING
1148
+ REGISTERED
1149
+ CONVERTED
1150
+ REWARDED
1151
+ }
1152
+
1068
1153
  // ============================================
1069
1154
  // CANVAS DOCUMENT MODELS
1070
1155
  // ============================================
@@ -1308,3 +1393,233 @@ model AppInvite {
1308
1393
  @@index([token])
1309
1394
  @@index([isActive])
1310
1395
  }
1396
+
1397
+ // ============================================
1398
+ // EMAIL LIFECYCLE MODELS
1399
+ // ============================================
1400
+
1401
+ model EmailSend {
1402
+ id String @id @default(cuid())
1403
+ userId String?
1404
+ templateSlug String
1405
+ campaignId String?
1406
+
1407
+ status EmailStatus @default(QUEUED)
1408
+ sentAt DateTime?
1409
+ deliveredAt DateTime?
1410
+ toEmail String
1411
+ fromEmail String @default("info@mail.smartlex.sk")
1412
+
1413
+ openedAt DateTime?
1414
+ openCount Int @default(0)
1415
+ clickedAt DateTime?
1416
+ clickCount Int @default(0)
1417
+
1418
+ bouncedAt DateTime?
1419
+ bounceType BounceType?
1420
+ complainedAt DateTime?
1421
+ unsubscribedAt DateTime?
1422
+
1423
+ errorMessage String?
1424
+ errorCode String?
1425
+
1426
+ subjectLine String?
1427
+ espMessageId String? @unique
1428
+ idempotencyKey String? @unique
1429
+ isTransactional Boolean @default(false)
1430
+ metadata Json?
1431
+
1432
+ createdAt DateTime @default(now())
1433
+
1434
+ user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
1435
+
1436
+ @@index([userId])
1437
+ @@index([templateSlug])
1438
+ @@index([status])
1439
+ @@index([sentAt])
1440
+ @@index([campaignId])
1441
+ @@index([toEmail])
1442
+ @@index([userId, sentAt])
1443
+ @@index([status, sentAt])
1444
+ @@index([userId, isTransactional, sentAt])
1445
+ }
1446
+
1447
+ model EmailSequence {
1448
+ id String @id @default(cuid())
1449
+ slug String @unique
1450
+ name String
1451
+ description String?
1452
+ triggerEvent String
1453
+ isActive Boolean @default(true)
1454
+
1455
+ steps EmailSequenceStep[]
1456
+ enrollments EmailSequenceEnrollment[]
1457
+
1458
+ createdAt DateTime @default(now())
1459
+ updatedAt DateTime @updatedAt
1460
+ }
1461
+
1462
+ model EmailSequenceStep {
1463
+ id String @id @default(cuid())
1464
+ sequenceId String
1465
+ templateSlug String
1466
+ stepOrder Int
1467
+ delayMinutes Int @default(0)
1468
+ sendCondition Json?
1469
+ skipCondition Json?
1470
+ isActive Boolean @default(true)
1471
+
1472
+ sequence EmailSequence @relation(fields: [sequenceId], references: [id], onDelete: Cascade)
1473
+
1474
+ createdAt DateTime @default(now())
1475
+
1476
+ @@unique([sequenceId, stepOrder])
1477
+ }
1478
+
1479
+ model EmailSequenceEnrollment {
1480
+ id String @id @default(cuid())
1481
+ userId String
1482
+ sequenceId String
1483
+ currentStep Int @default(0)
1484
+ status EnrollmentStatus @default(ACTIVE)
1485
+
1486
+ enrolledAt DateTime @default(now())
1487
+ completedAt DateTime?
1488
+ exitedAt DateTime?
1489
+ exitReason String?
1490
+ nextSendAt DateTime?
1491
+ processingLockedAt DateTime?
1492
+
1493
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1494
+ sequence EmailSequence @relation(fields: [sequenceId], references: [id])
1495
+
1496
+ @@index([status, nextSendAt])
1497
+ @@index([userId, sequenceId, status])
1498
+ }
1499
+
1500
+ model EmailConsent {
1501
+ id String @id @default(cuid())
1502
+ userId String @unique
1503
+
1504
+ marketingConsent Boolean @default(false)
1505
+ productUpdatesConsent Boolean @default(false)
1506
+ legalNewsConsent Boolean @default(false)
1507
+
1508
+ doubleOptinSentAt DateTime?
1509
+ doubleOptinConfirmedAt DateTime?
1510
+ doubleOptinToken String? @unique
1511
+
1512
+ user User @relation(fields: [userId], references: [id], onDelete: Restrict)
1513
+ history EmailConsentHistory[]
1514
+
1515
+ createdAt DateTime @default(now())
1516
+ updatedAt DateTime @updatedAt
1517
+ }
1518
+
1519
+ model EmailConsentHistory {
1520
+ id String @id @default(cuid())
1521
+ consentId String
1522
+ userId String
1523
+
1524
+ action ConsentAction
1525
+ channel String
1526
+ oldValue Boolean?
1527
+ newValue Boolean
1528
+
1529
+ consentText String
1530
+ consentVersion String
1531
+ consentMethod String
1532
+ ipAddress String?
1533
+ userAgent String?
1534
+
1535
+ consent EmailConsent @relation(fields: [consentId], references: [id], onDelete: Restrict)
1536
+
1537
+ createdAt DateTime @default(now())
1538
+
1539
+ @@index([userId, createdAt])
1540
+ @@index([consentId])
1541
+ @@index([createdAt])
1542
+ }
1543
+
1544
+ model UserScore {
1545
+ id String @id @default(cuid())
1546
+ userId String @unique
1547
+ score Int @default(0)
1548
+ leadSegment LeadSegment @default(COLD)
1549
+ churnRisk ChurnRisk @default(NONE)
1550
+
1551
+ signupScore Int @default(0)
1552
+ engagementScore Int @default(0)
1553
+ usageScore Int @default(0)
1554
+ intentScore Int @default(0)
1555
+ decayScore Int @default(0)
1556
+
1557
+ lastActivityAt DateTime?
1558
+ scoreUpdatedAt DateTime @default(now())
1559
+
1560
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1561
+ events UserScoreEvent[]
1562
+
1563
+ createdAt DateTime @default(now())
1564
+
1565
+ @@index([score])
1566
+ @@index([leadSegment])
1567
+ @@index([churnRisk])
1568
+ }
1569
+
1570
+ model UserScoreEvent {
1571
+ id String @id @default(cuid())
1572
+ userScoreId String
1573
+ userId String
1574
+ eventType String
1575
+ pointsDelta Int
1576
+ scoreBefore Int
1577
+ scoreAfter Int
1578
+ metadata Json?
1579
+
1580
+ userScore UserScore @relation(fields: [userScoreId], references: [id], onDelete: Cascade)
1581
+
1582
+ createdAt DateTime @default(now())
1583
+
1584
+ @@index([userId, createdAt])
1585
+ @@index([userScoreId])
1586
+ @@index([eventType])
1587
+ @@index([createdAt])
1588
+ }
1589
+
1590
+ model EmailSuppression {
1591
+ id String @id @default(cuid())
1592
+ email String
1593
+ reason SuppressionReason
1594
+ source String?
1595
+
1596
+ suppressedAt DateTime @default(now())
1597
+
1598
+ @@unique([email, reason])
1599
+ @@index([email])
1600
+ }
1601
+
1602
+ model Referral {
1603
+ id String @id @default(cuid())
1604
+ referrerId String?
1605
+ referredUserId String?
1606
+ referredEmail String?
1607
+ status ReferralStatus @default(PENDING)
1608
+ referralCode String @unique
1609
+
1610
+ rewardType String?
1611
+ rewardedAt DateTime?
1612
+ clickCount Int @default(0)
1613
+ registeredAt DateTime?
1614
+ convertedAt DateTime?
1615
+
1616
+ createdAt DateTime @default(now())
1617
+
1618
+ referrer User? @relation("ReferralsSent", fields: [referrerId], references: [id], onDelete: SetNull)
1619
+ referredUser User? @relation("ReferralsReceived", fields: [referredUserId], references: [id], onDelete: SetNull)
1620
+
1621
+ @@index([referrerId])
1622
+ @@index([referredUserId])
1623
+ @@index([referralCode])
1624
+ @@index([status])
1625
+ }
@@ -1,2 +1,2 @@
1
1
  -- AlterTable
2
- ALTER TABLE "User" ADD COLUMN "phoneVerified" TIMESTAMP(3);
2
+ ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "phoneVerified" TIMESTAMP(3);
@@ -1,5 +1,5 @@
1
1
  -- CreateTable
2
- CREATE TABLE "PhoneOtp" (
2
+ CREATE TABLE IF NOT EXISTS "PhoneOtp" (
3
3
  "id" TEXT NOT NULL,
4
4
  "userId" TEXT NOT NULL,
5
5
  "phone" TEXT NOT NULL,
@@ -11,7 +11,7 @@ CREATE TABLE "PhoneOtp" (
11
11
  );
12
12
 
13
13
  -- CreateTable
14
- CREATE TABLE "AdminGrant" (
14
+ CREATE TABLE IF NOT EXISTS "AdminGrant" (
15
15
  "id" TEXT NOT NULL,
16
16
  "userId" TEXT,
17
17
  "organizationId" TEXT,
@@ -31,7 +31,7 @@ CREATE TABLE "AdminGrant" (
31
31
  );
32
32
 
33
33
  -- CreateTable
34
- CREATE TABLE "AppInvite" (
34
+ CREATE TABLE IF NOT EXISTS "AppInvite" (
35
35
  "id" TEXT NOT NULL,
36
36
  "email" TEXT NOT NULL,
37
37
  "token" TEXT NOT NULL,
@@ -52,49 +52,70 @@ CREATE TABLE "AppInvite" (
52
52
  );
53
53
 
54
54
  -- CreateIndex
55
- CREATE UNIQUE INDEX "PhoneOtp_bulkgateId_key" ON "PhoneOtp"("bulkgateId");
55
+ CREATE UNIQUE INDEX IF NOT EXISTS "PhoneOtp_bulkgateId_key" ON "PhoneOtp"("bulkgateId");
56
56
 
57
57
  -- CreateIndex
58
- CREATE INDEX "PhoneOtp_userId_idx" ON "PhoneOtp"("userId");
58
+ CREATE INDEX IF NOT EXISTS "PhoneOtp_userId_idx" ON "PhoneOtp"("userId");
59
59
 
60
60
  -- CreateIndex
61
- CREATE INDEX "PhoneOtp_bulkgateId_idx" ON "PhoneOtp"("bulkgateId");
61
+ CREATE INDEX IF NOT EXISTS "PhoneOtp_bulkgateId_idx" ON "PhoneOtp"("bulkgateId");
62
62
 
63
63
  -- CreateIndex
64
- CREATE INDEX "PhoneOtp_expiresAt_idx" ON "PhoneOtp"("expiresAt");
64
+ CREATE INDEX IF NOT EXISTS "PhoneOtp_expiresAt_idx" ON "PhoneOtp"("expiresAt");
65
65
 
66
66
  -- CreateIndex
67
- CREATE INDEX "AdminGrant_userId_idx" ON "AdminGrant"("userId");
67
+ CREATE INDEX IF NOT EXISTS "AdminGrant_userId_idx" ON "AdminGrant"("userId");
68
68
 
69
69
  -- CreateIndex
70
- CREATE INDEX "AdminGrant_organizationId_idx" ON "AdminGrant"("organizationId");
70
+ CREATE INDEX IF NOT EXISTS "AdminGrant_organizationId_idx" ON "AdminGrant"("organizationId");
71
71
 
72
72
  -- CreateIndex
73
- CREATE INDEX "AdminGrant_expiresAt_idx" ON "AdminGrant"("expiresAt");
73
+ CREATE INDEX IF NOT EXISTS "AdminGrant_expiresAt_idx" ON "AdminGrant"("expiresAt");
74
74
 
75
75
  -- CreateIndex
76
- CREATE INDEX "AdminGrant_isActive_idx" ON "AdminGrant"("isActive");
76
+ CREATE INDEX IF NOT EXISTS "AdminGrant_isActive_idx" ON "AdminGrant"("isActive");
77
77
 
78
78
  -- CreateIndex
79
- CREATE UNIQUE INDEX "AppInvite_token_key" ON "AppInvite"("token");
79
+ CREATE UNIQUE INDEX IF NOT EXISTS "AppInvite_token_key" ON "AppInvite"("token");
80
80
 
81
81
  -- CreateIndex
82
- CREATE INDEX "AppInvite_email_idx" ON "AppInvite"("email");
82
+ CREATE INDEX IF NOT EXISTS "AppInvite_email_idx" ON "AppInvite"("email");
83
83
 
84
84
  -- CreateIndex
85
- CREATE INDEX "AppInvite_token_idx" ON "AppInvite"("token");
85
+ CREATE INDEX IF NOT EXISTS "AppInvite_token_idx" ON "AppInvite"("token");
86
86
 
87
87
  -- CreateIndex
88
- CREATE INDEX "AppInvite_isActive_idx" ON "AppInvite"("isActive");
88
+ CREATE INDEX IF NOT EXISTS "AppInvite_isActive_idx" ON "AppInvite"("isActive");
89
89
 
90
90
  -- CreateIndex
91
- CREATE INDEX "User_phone_idx" ON "User"("phone");
92
-
93
- -- AddForeignKey
94
- ALTER TABLE "PhoneOtp" ADD CONSTRAINT "PhoneOtp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
95
-
96
- -- AddForeignKey
97
- ALTER TABLE "AdminGrant" ADD CONSTRAINT "AdminGrant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
98
-
99
- -- AddForeignKey
100
- ALTER TABLE "AdminGrant" ADD CONSTRAINT "AdminGrant_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
91
+ CREATE INDEX IF NOT EXISTS "User_phone_idx" ON "User"("phone");
92
+
93
+ -- AddForeignKey (idempotent)
94
+ DO $$ BEGIN
95
+ IF NOT EXISTS (
96
+ SELECT 1 FROM pg_constraint WHERE conname = 'PhoneOtp_userId_fkey'
97
+ ) THEN
98
+ ALTER TABLE "PhoneOtp" ADD CONSTRAINT "PhoneOtp_userId_fkey"
99
+ FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
100
+ END IF;
101
+ END $$;
102
+
103
+ -- AddForeignKey (idempotent)
104
+ DO $$ BEGIN
105
+ IF NOT EXISTS (
106
+ SELECT 1 FROM pg_constraint WHERE conname = 'AdminGrant_userId_fkey'
107
+ ) THEN
108
+ ALTER TABLE "AdminGrant" ADD CONSTRAINT "AdminGrant_userId_fkey"
109
+ FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
110
+ END IF;
111
+ END $$;
112
+
113
+ -- AddForeignKey (idempotent)
114
+ DO $$ BEGIN
115
+ IF NOT EXISTS (
116
+ SELECT 1 FROM pg_constraint WHERE conname = 'AdminGrant_organizationId_fkey'
117
+ ) THEN
118
+ ALTER TABLE "AdminGrant" ADD CONSTRAINT "AdminGrant_organizationId_fkey"
119
+ FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
120
+ END IF;
121
+ END $$;
@@ -0,0 +1,372 @@
1
+ /*
2
+ Warnings:
3
+
4
+ - A unique constraint covering the columns `[referralCode]` on the table `User` will be added. If there are existing duplicate values, this will fail.
5
+
6
+ */
7
+ -- CreateEnum
8
+ CREATE TYPE "LifecycleStage" AS ENUM ('REGISTERED', 'ACTIVATED', 'PAYING', 'SUSPENDED', 'CHURNED', 'REACTIVATED');
9
+
10
+ -- CreateEnum
11
+ CREATE TYPE "EmailStatus" AS ENUM ('QUEUED', 'SENT', 'DELIVERED', 'BOUNCED', 'FAILED', 'COMPLAINED');
12
+
13
+ -- CreateEnum
14
+ CREATE TYPE "EnrollmentStatus" AS ENUM ('ACTIVE', 'COMPLETED', 'EXITED', 'PAUSED');
15
+
16
+ -- CreateEnum
17
+ CREATE TYPE "LeadSegment" AS ENUM ('COLD', 'WARM', 'HOT', 'CONVERTING');
18
+
19
+ -- CreateEnum
20
+ CREATE TYPE "ChurnRisk" AS ENUM ('NONE', 'LOW', 'MEDIUM', 'HIGH');
21
+
22
+ -- CreateEnum
23
+ CREATE TYPE "BounceType" AS ENUM ('HARD', 'SOFT');
24
+
25
+ -- CreateEnum
26
+ CREATE TYPE "ConsentAction" AS ENUM ('GRANTED', 'WITHDRAWN', 'MODIFIED');
27
+
28
+ -- CreateEnum
29
+ CREATE TYPE "SuppressionReason" AS ENUM ('HARD_BOUNCE', 'COMPLAINT', 'MANUAL', 'SUNSET');
30
+
31
+ -- CreateEnum
32
+ CREATE TYPE "ReferralStatus" AS ENUM ('PENDING', 'REGISTERED', 'CONVERTED', 'REWARDED');
33
+
34
+ -- AlterTable
35
+ ALTER TABLE "User" ADD COLUMN "activatedAt" TIMESTAMP(3),
36
+ ADD COLUMN "churnRiskScore" INTEGER NOT NULL DEFAULT 0,
37
+ ADD COLUMN "churnRiskUpdatedAt" TIMESTAMP(3),
38
+ ADD COLUMN "isEmailSuppressed" BOOLEAN NOT NULL DEFAULT false,
39
+ ADD COLUMN "lastActiveAt" TIMESTAMP(3),
40
+ ADD COLUMN "lastEmailSentAt" TIMESTAMP(3),
41
+ ADD COLUMN "lastLoginAt" TIMESTAMP(3),
42
+ ADD COLUMN "lastMarketingEmailSentAt" TIMESTAMP(3),
43
+ ADD COLUMN "lifecycleStage" "LifecycleStage" NOT NULL DEFAULT 'REGISTERED',
44
+ ADD COLUMN "lifetimeValue" INTEGER NOT NULL DEFAULT 0,
45
+ ADD COLUMN "referralCode" TEXT;
46
+
47
+ -- CreateTable
48
+ CREATE TABLE "EmailSend" (
49
+ "id" TEXT NOT NULL,
50
+ "userId" TEXT,
51
+ "templateSlug" TEXT NOT NULL,
52
+ "campaignId" TEXT,
53
+ "status" "EmailStatus" NOT NULL DEFAULT 'QUEUED',
54
+ "sentAt" TIMESTAMP(3),
55
+ "deliveredAt" TIMESTAMP(3),
56
+ "toEmail" TEXT NOT NULL,
57
+ "fromEmail" TEXT NOT NULL DEFAULT 'info@mail.smartlex.sk',
58
+ "openedAt" TIMESTAMP(3),
59
+ "openCount" INTEGER NOT NULL DEFAULT 0,
60
+ "clickedAt" TIMESTAMP(3),
61
+ "clickCount" INTEGER NOT NULL DEFAULT 0,
62
+ "bouncedAt" TIMESTAMP(3),
63
+ "bounceType" "BounceType",
64
+ "complainedAt" TIMESTAMP(3),
65
+ "unsubscribedAt" TIMESTAMP(3),
66
+ "errorMessage" TEXT,
67
+ "errorCode" TEXT,
68
+ "subjectLine" TEXT,
69
+ "espMessageId" TEXT,
70
+ "idempotencyKey" TEXT,
71
+ "isTransactional" BOOLEAN NOT NULL DEFAULT false,
72
+ "metadata" JSONB,
73
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
74
+
75
+ CONSTRAINT "EmailSend_pkey" PRIMARY KEY ("id")
76
+ );
77
+
78
+ -- CreateTable
79
+ CREATE TABLE "EmailSequence" (
80
+ "id" TEXT NOT NULL,
81
+ "slug" TEXT NOT NULL,
82
+ "name" TEXT NOT NULL,
83
+ "description" TEXT,
84
+ "triggerEvent" TEXT NOT NULL,
85
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
86
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
87
+ "updatedAt" TIMESTAMP(3) NOT NULL,
88
+
89
+ CONSTRAINT "EmailSequence_pkey" PRIMARY KEY ("id")
90
+ );
91
+
92
+ -- CreateTable
93
+ CREATE TABLE "EmailSequenceStep" (
94
+ "id" TEXT NOT NULL,
95
+ "sequenceId" TEXT NOT NULL,
96
+ "templateSlug" TEXT NOT NULL,
97
+ "stepOrder" INTEGER NOT NULL,
98
+ "delayMinutes" INTEGER NOT NULL DEFAULT 0,
99
+ "sendCondition" JSONB,
100
+ "skipCondition" JSONB,
101
+ "isActive" BOOLEAN NOT NULL DEFAULT true,
102
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
103
+
104
+ CONSTRAINT "EmailSequenceStep_pkey" PRIMARY KEY ("id")
105
+ );
106
+
107
+ -- CreateTable
108
+ CREATE TABLE "EmailSequenceEnrollment" (
109
+ "id" TEXT NOT NULL,
110
+ "userId" TEXT NOT NULL,
111
+ "sequenceId" TEXT NOT NULL,
112
+ "currentStep" INTEGER NOT NULL DEFAULT 0,
113
+ "status" "EnrollmentStatus" NOT NULL DEFAULT 'ACTIVE',
114
+ "enrolledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
115
+ "completedAt" TIMESTAMP(3),
116
+ "exitedAt" TIMESTAMP(3),
117
+ "exitReason" TEXT,
118
+ "nextSendAt" TIMESTAMP(3),
119
+ "processingLockedAt" TIMESTAMP(3),
120
+
121
+ CONSTRAINT "EmailSequenceEnrollment_pkey" PRIMARY KEY ("id")
122
+ );
123
+
124
+ -- CreateTable
125
+ CREATE TABLE "EmailConsent" (
126
+ "id" TEXT NOT NULL,
127
+ "userId" TEXT NOT NULL,
128
+ "marketingConsent" BOOLEAN NOT NULL DEFAULT false,
129
+ "productUpdatesConsent" BOOLEAN NOT NULL DEFAULT false,
130
+ "legalNewsConsent" BOOLEAN NOT NULL DEFAULT false,
131
+ "doubleOptinSentAt" TIMESTAMP(3),
132
+ "doubleOptinConfirmedAt" TIMESTAMP(3),
133
+ "doubleOptinToken" TEXT,
134
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
135
+ "updatedAt" TIMESTAMP(3) NOT NULL,
136
+
137
+ CONSTRAINT "EmailConsent_pkey" PRIMARY KEY ("id")
138
+ );
139
+
140
+ -- CreateTable
141
+ CREATE TABLE "EmailConsentHistory" (
142
+ "id" TEXT NOT NULL,
143
+ "consentId" TEXT NOT NULL,
144
+ "userId" TEXT NOT NULL,
145
+ "action" "ConsentAction" NOT NULL,
146
+ "channel" TEXT NOT NULL,
147
+ "oldValue" BOOLEAN,
148
+ "newValue" BOOLEAN NOT NULL,
149
+ "consentText" TEXT NOT NULL,
150
+ "consentVersion" TEXT NOT NULL,
151
+ "consentMethod" TEXT NOT NULL,
152
+ "ipAddress" TEXT,
153
+ "userAgent" TEXT,
154
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
155
+
156
+ CONSTRAINT "EmailConsentHistory_pkey" PRIMARY KEY ("id")
157
+ );
158
+
159
+ -- CreateTable
160
+ CREATE TABLE "UserScore" (
161
+ "id" TEXT NOT NULL,
162
+ "userId" TEXT NOT NULL,
163
+ "score" INTEGER NOT NULL DEFAULT 0,
164
+ "leadSegment" "LeadSegment" NOT NULL DEFAULT 'COLD',
165
+ "churnRisk" "ChurnRisk" NOT NULL DEFAULT 'NONE',
166
+ "signupScore" INTEGER NOT NULL DEFAULT 0,
167
+ "engagementScore" INTEGER NOT NULL DEFAULT 0,
168
+ "usageScore" INTEGER NOT NULL DEFAULT 0,
169
+ "intentScore" INTEGER NOT NULL DEFAULT 0,
170
+ "decayScore" INTEGER NOT NULL DEFAULT 0,
171
+ "lastActivityAt" TIMESTAMP(3),
172
+ "scoreUpdatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
173
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
174
+
175
+ CONSTRAINT "UserScore_pkey" PRIMARY KEY ("id")
176
+ );
177
+
178
+ -- CreateTable
179
+ CREATE TABLE "UserScoreEvent" (
180
+ "id" TEXT NOT NULL,
181
+ "userScoreId" TEXT NOT NULL,
182
+ "userId" TEXT NOT NULL,
183
+ "eventType" TEXT NOT NULL,
184
+ "pointsDelta" INTEGER NOT NULL,
185
+ "scoreBefore" INTEGER NOT NULL,
186
+ "scoreAfter" INTEGER NOT NULL,
187
+ "metadata" JSONB,
188
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
189
+
190
+ CONSTRAINT "UserScoreEvent_pkey" PRIMARY KEY ("id")
191
+ );
192
+
193
+ -- CreateTable
194
+ CREATE TABLE "EmailSuppression" (
195
+ "id" TEXT NOT NULL,
196
+ "email" TEXT NOT NULL,
197
+ "reason" "SuppressionReason" NOT NULL,
198
+ "source" TEXT,
199
+ "suppressedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
200
+
201
+ CONSTRAINT "EmailSuppression_pkey" PRIMARY KEY ("id")
202
+ );
203
+
204
+ -- CreateTable
205
+ CREATE TABLE "Referral" (
206
+ "id" TEXT NOT NULL,
207
+ "referrerId" TEXT,
208
+ "referredUserId" TEXT,
209
+ "referredEmail" TEXT,
210
+ "status" "ReferralStatus" NOT NULL DEFAULT 'PENDING',
211
+ "referralCode" TEXT NOT NULL,
212
+ "rewardType" TEXT,
213
+ "rewardedAt" TIMESTAMP(3),
214
+ "clickCount" INTEGER NOT NULL DEFAULT 0,
215
+ "registeredAt" TIMESTAMP(3),
216
+ "convertedAt" TIMESTAMP(3),
217
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
218
+
219
+ CONSTRAINT "Referral_pkey" PRIMARY KEY ("id")
220
+ );
221
+
222
+ -- CreateIndex
223
+ CREATE UNIQUE INDEX "EmailSend_espMessageId_key" ON "EmailSend"("espMessageId");
224
+
225
+ -- CreateIndex
226
+ CREATE UNIQUE INDEX "EmailSend_idempotencyKey_key" ON "EmailSend"("idempotencyKey");
227
+
228
+ -- CreateIndex
229
+ CREATE INDEX "EmailSend_userId_idx" ON "EmailSend"("userId");
230
+
231
+ -- CreateIndex
232
+ CREATE INDEX "EmailSend_templateSlug_idx" ON "EmailSend"("templateSlug");
233
+
234
+ -- CreateIndex
235
+ CREATE INDEX "EmailSend_status_idx" ON "EmailSend"("status");
236
+
237
+ -- CreateIndex
238
+ CREATE INDEX "EmailSend_sentAt_idx" ON "EmailSend"("sentAt");
239
+
240
+ -- CreateIndex
241
+ CREATE INDEX "EmailSend_campaignId_idx" ON "EmailSend"("campaignId");
242
+
243
+ -- CreateIndex
244
+ CREATE INDEX "EmailSend_toEmail_idx" ON "EmailSend"("toEmail");
245
+
246
+ -- CreateIndex
247
+ CREATE INDEX "EmailSend_userId_sentAt_idx" ON "EmailSend"("userId", "sentAt");
248
+
249
+ -- CreateIndex
250
+ CREATE INDEX "EmailSend_status_sentAt_idx" ON "EmailSend"("status", "sentAt");
251
+
252
+ -- CreateIndex
253
+ CREATE INDEX "EmailSend_userId_isTransactional_sentAt_idx" ON "EmailSend"("userId", "isTransactional", "sentAt");
254
+
255
+ -- CreateIndex
256
+ CREATE UNIQUE INDEX "EmailSequence_slug_key" ON "EmailSequence"("slug");
257
+
258
+ -- CreateIndex
259
+ CREATE UNIQUE INDEX "EmailSequenceStep_sequenceId_stepOrder_key" ON "EmailSequenceStep"("sequenceId", "stepOrder");
260
+
261
+ -- CreateIndex
262
+ CREATE INDEX "EmailSequenceEnrollment_status_nextSendAt_idx" ON "EmailSequenceEnrollment"("status", "nextSendAt");
263
+
264
+ -- CreateIndex
265
+ CREATE INDEX "EmailSequenceEnrollment_userId_sequenceId_status_idx" ON "EmailSequenceEnrollment"("userId", "sequenceId", "status");
266
+
267
+ -- CreateIndex
268
+ CREATE UNIQUE INDEX "EmailConsent_userId_key" ON "EmailConsent"("userId");
269
+
270
+ -- CreateIndex
271
+ CREATE UNIQUE INDEX "EmailConsent_doubleOptinToken_key" ON "EmailConsent"("doubleOptinToken");
272
+
273
+ -- CreateIndex
274
+ CREATE INDEX "EmailConsentHistory_userId_createdAt_idx" ON "EmailConsentHistory"("userId", "createdAt");
275
+
276
+ -- CreateIndex
277
+ CREATE INDEX "EmailConsentHistory_consentId_idx" ON "EmailConsentHistory"("consentId");
278
+
279
+ -- CreateIndex
280
+ CREATE INDEX "EmailConsentHistory_createdAt_idx" ON "EmailConsentHistory"("createdAt");
281
+
282
+ -- CreateIndex
283
+ CREATE UNIQUE INDEX "UserScore_userId_key" ON "UserScore"("userId");
284
+
285
+ -- CreateIndex
286
+ CREATE INDEX "UserScore_score_idx" ON "UserScore"("score");
287
+
288
+ -- CreateIndex
289
+ CREATE INDEX "UserScore_leadSegment_idx" ON "UserScore"("leadSegment");
290
+
291
+ -- CreateIndex
292
+ CREATE INDEX "UserScore_churnRisk_idx" ON "UserScore"("churnRisk");
293
+
294
+ -- CreateIndex
295
+ CREATE INDEX "UserScoreEvent_userId_createdAt_idx" ON "UserScoreEvent"("userId", "createdAt");
296
+
297
+ -- CreateIndex
298
+ CREATE INDEX "UserScoreEvent_userScoreId_idx" ON "UserScoreEvent"("userScoreId");
299
+
300
+ -- CreateIndex
301
+ CREATE INDEX "UserScoreEvent_eventType_idx" ON "UserScoreEvent"("eventType");
302
+
303
+ -- CreateIndex
304
+ CREATE INDEX "UserScoreEvent_createdAt_idx" ON "UserScoreEvent"("createdAt");
305
+
306
+ -- CreateIndex
307
+ CREATE INDEX "EmailSuppression_email_idx" ON "EmailSuppression"("email");
308
+
309
+ -- CreateIndex
310
+ CREATE UNIQUE INDEX "EmailSuppression_email_reason_key" ON "EmailSuppression"("email", "reason");
311
+
312
+ -- CreateIndex
313
+ CREATE UNIQUE INDEX "Referral_referralCode_key" ON "Referral"("referralCode");
314
+
315
+ -- CreateIndex
316
+ CREATE INDEX "Referral_referrerId_idx" ON "Referral"("referrerId");
317
+
318
+ -- CreateIndex
319
+ CREATE INDEX "Referral_referredUserId_idx" ON "Referral"("referredUserId");
320
+
321
+ -- CreateIndex
322
+ CREATE INDEX "Referral_referralCode_idx" ON "Referral"("referralCode");
323
+
324
+ -- CreateIndex
325
+ CREATE INDEX "Referral_status_idx" ON "Referral"("status");
326
+
327
+ -- CreateIndex
328
+ CREATE UNIQUE INDEX "User_referralCode_key" ON "User"("referralCode");
329
+
330
+ -- CreateIndex
331
+ CREATE INDEX "User_lifecycleStage_idx" ON "User"("lifecycleStage");
332
+
333
+ -- CreateIndex
334
+ CREATE INDEX "User_lastActiveAt_idx" ON "User"("lastActiveAt");
335
+
336
+ -- CreateIndex
337
+ CREATE INDEX "User_isEmailSuppressed_idx" ON "User"("isEmailSuppressed");
338
+
339
+ -- AddForeignKey
340
+ ALTER TABLE "EmailSend" ADD CONSTRAINT "EmailSend_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
341
+
342
+ -- AddForeignKey
343
+ ALTER TABLE "EmailSequenceStep" ADD CONSTRAINT "EmailSequenceStep_sequenceId_fkey" FOREIGN KEY ("sequenceId") REFERENCES "EmailSequence"("id") ON DELETE CASCADE ON UPDATE CASCADE;
344
+
345
+ -- AddForeignKey
346
+ ALTER TABLE "EmailSequenceEnrollment" ADD CONSTRAINT "EmailSequenceEnrollment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
347
+
348
+ -- AddForeignKey
349
+ ALTER TABLE "EmailSequenceEnrollment" ADD CONSTRAINT "EmailSequenceEnrollment_sequenceId_fkey" FOREIGN KEY ("sequenceId") REFERENCES "EmailSequence"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
350
+
351
+ -- AddForeignKey
352
+ ALTER TABLE "EmailConsent" ADD CONSTRAINT "EmailConsent_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
353
+
354
+ -- AddForeignKey
355
+ ALTER TABLE "EmailConsentHistory" ADD CONSTRAINT "EmailConsentHistory_consentId_fkey" FOREIGN KEY ("consentId") REFERENCES "EmailConsent"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
356
+
357
+ -- AddForeignKey
358
+ ALTER TABLE "UserScore" ADD CONSTRAINT "UserScore_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
359
+
360
+ -- AddForeignKey
361
+ ALTER TABLE "UserScoreEvent" ADD CONSTRAINT "UserScoreEvent_userScoreId_fkey" FOREIGN KEY ("userScoreId") REFERENCES "UserScore"("id") ON DELETE CASCADE ON UPDATE CASCADE;
362
+
363
+ -- AddForeignKey
364
+ ALTER TABLE "Referral" ADD CONSTRAINT "Referral_referrerId_fkey" FOREIGN KEY ("referrerId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
365
+
366
+ -- AddForeignKey
367
+ ALTER TABLE "Referral" ADD CONSTRAINT "Referral_referredUserId_fkey" FOREIGN KEY ("referredUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
368
+
369
+ -- Partial unique index: max 1 ACTIVE enrollment per user per sequence
370
+ CREATE UNIQUE INDEX IF NOT EXISTS "enrollment_active_unique"
371
+ ON "EmailSequenceEnrollment" ("userId", "sequenceId")
372
+ WHERE status = 'ACTIVE';