feat(admin): consentement aux annonces et désabonnement sans connexion
Les comptes déjà créés ne sont pas inscrits. Le choix est facultatif à l’inscription et dans les paramètres. Un lien public demande confirmation avant d’arrêter les actualités, sans toucher aux messages de compte.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
-- Additive marketing consent and suppression. No existing user is opted in.
|
||||
|
||||
CREATE TABLE "MarketingPreference" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"emailNormalized" TEXT NOT NULL,
|
||||
"optedIn" BOOLEAN NOT NULL DEFAULT false,
|
||||
"preferredLanguage" TEXT,
|
||||
"consentVersion" TEXT,
|
||||
"consentText" TEXT,
|
||||
"source" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "MarketingPreference_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "MarketingPreference_userId_key" ON "MarketingPreference"("userId");
|
||||
CREATE INDEX "MarketingPreference_emailNormalized_idx" ON "MarketingPreference"("emailNormalized");
|
||||
CREATE INDEX "MarketingPreference_optedIn_idx" ON "MarketingPreference"("optedIn");
|
||||
|
||||
ALTER TABLE "MarketingPreference" ADD CONSTRAINT "MarketingPreference_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "MarketingPreferenceEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"preferenceId" TEXT NOT NULL,
|
||||
"userId" TEXT,
|
||||
"emailNormalized" TEXT NOT NULL,
|
||||
"optedIn" BOOLEAN NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"consentVersion" TEXT,
|
||||
"consentText" TEXT,
|
||||
"ip" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "MarketingPreferenceEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "MarketingPreferenceEvent_preferenceId_idx" ON "MarketingPreferenceEvent"("preferenceId");
|
||||
CREATE INDEX "MarketingPreferenceEvent_userId_createdAt_idx" ON "MarketingPreferenceEvent"("userId", "createdAt");
|
||||
CREATE INDEX "MarketingPreferenceEvent_emailNormalized_createdAt_idx" ON "MarketingPreferenceEvent"("emailNormalized", "createdAt");
|
||||
|
||||
ALTER TABLE "MarketingPreferenceEvent" ADD CONSTRAINT "MarketingPreferenceEvent_preferenceId_fkey" FOREIGN KEY ("preferenceId") REFERENCES "MarketingPreference"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE "EmailSuppression" (
|
||||
"id" TEXT NOT NULL,
|
||||
"emailNormalized" TEXT NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"userId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "EmailSuppression_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "EmailSuppression_emailNormalized_key" ON "EmailSuppression"("emailNormalized");
|
||||
CREATE INDEX "EmailSuppression_reason_idx" ON "EmailSuppression"("reason");
|
||||
CREATE INDEX "EmailSuppression_userId_idx" ON "EmailSuppression"("userId");
|
||||
|
||||
ALTER TABLE "EmailSuppression" ADD CONSTRAINT "EmailSuppression_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -54,6 +54,8 @@ model User {
|
||||
usageLogs UsageLog[]
|
||||
apiKeys UserAPIKey[]
|
||||
aiConsentLogs AiConsentLog[]
|
||||
marketingPreference MarketingPreference?
|
||||
emailSuppressions EmailSuppression[]
|
||||
noteClusters NoteCluster[]
|
||||
bridgeNotes BridgeNote[]
|
||||
bridgeSuggestions BridgeSuggestion[]
|
||||
@@ -1124,3 +1126,55 @@ model ErrorLog {
|
||||
@@index([createdAt])
|
||||
@@index([resolved])
|
||||
}
|
||||
|
||||
/// Explicit marketing announcements opt-in. Missing row = not opted in.
|
||||
model MarketingPreference {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
emailNormalized String
|
||||
optedIn Boolean @default(false)
|
||||
preferredLanguage String?
|
||||
consentVersion String?
|
||||
consentText String? @db.Text
|
||||
source String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
events MarketingPreferenceEvent[]
|
||||
|
||||
@@index([emailNormalized])
|
||||
@@index([optedIn])
|
||||
}
|
||||
|
||||
model MarketingPreferenceEvent {
|
||||
id String @id @default(cuid())
|
||||
preferenceId String
|
||||
userId String?
|
||||
emailNormalized String
|
||||
optedIn Boolean
|
||||
source String
|
||||
consentVersion String?
|
||||
consentText String? @db.Text
|
||||
ip String?
|
||||
userAgent String?
|
||||
createdAt DateTime @default(now())
|
||||
preference MarketingPreference @relation(fields: [preferenceId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([preferenceId])
|
||||
@@index([userId, createdAt])
|
||||
@@index([emailNormalized, createdAt])
|
||||
}
|
||||
|
||||
/// Blocks campaign mail. Kept after account deletion (userId set null).
|
||||
model EmailSuppression {
|
||||
id String @id @default(cuid())
|
||||
emailNormalized String @unique
|
||||
reason String
|
||||
source String
|
||||
userId String?
|
||||
createdAt DateTime @default(now())
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([reason])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user