@danielcok17/prisma-db 1.16.1 → 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
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
|
+
}
|
|
@@ -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';
|