Aktueller Stand

This commit is contained in:
2026-01-23 14:01:49 +01:00
parent 2766dd12c5
commit e16f6d50fb
46 changed files with 5482 additions and 311 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE "CleanupJobCandidate"
ADD COLUMN "listUnsubscribePost" TEXT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "CleanupJobCandidate"
ADD COLUMN "unsubscribeDetails" JSONB;

View File

@@ -0,0 +1,16 @@
CREATE TABLE "TenantMetric" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"avgProcessingRate" DOUBLE PRECISION,
"sampleCount" INTEGER NOT NULL DEFAULT 0,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TenantMetric_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "TenantMetric_tenantId_key" ON "TenantMetric"("tenantId");
ALTER TABLE "TenantMetric"
ADD CONSTRAINT "TenantMetric_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id")
ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1 @@
ALTER TYPE "RuleConditionType" ADD VALUE IF NOT EXISTS 'HEADER_MISSING';

View File

@@ -0,0 +1,15 @@
ALTER TABLE "Rule" ADD COLUMN "position" INTEGER NOT NULL DEFAULT 0;
WITH ordered AS (
SELECT
"id",
"tenantId",
ROW_NUMBER() OVER (PARTITION BY "tenantId" ORDER BY "createdAt" ASC, "id" ASC) - 1 AS pos
FROM "Rule"
)
UPDATE "Rule" r
SET "position" = ordered.pos
FROM ordered
WHERE ordered.id = r.id;
CREATE INDEX "Rule_tenantId_position_idx" ON "Rule"("tenantId", "position");

View File

@@ -0,0 +1 @@
ALTER TABLE "Rule" ADD COLUMN "stopOnMatch" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,6 @@
ALTER TABLE "CleanupJob" ADD COLUMN "listingSeconds" INTEGER;
ALTER TABLE "CleanupJob" ADD COLUMN "processingSeconds" INTEGER;
ALTER TABLE "CleanupJob" ADD COLUMN "unsubscribeSeconds" INTEGER;
ALTER TABLE "CleanupJob" ADD COLUMN "routingSeconds" INTEGER;
ALTER TABLE "CleanupJob" ADD COLUMN "unsubscribeAttempts" INTEGER;
ALTER TABLE "CleanupJob" ADD COLUMN "actionAttempts" INTEGER;

View File

@@ -0,0 +1,23 @@
CREATE TABLE "TenantProviderMetric" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"provider" "MailProvider" NOT NULL,
"avgListingRate" DOUBLE PRECISION,
"avgProcessingRate" DOUBLE PRECISION,
"avgUnsubscribeRate" DOUBLE PRECISION,
"avgRoutingRate" DOUBLE PRECISION,
"listingSampleCount" INTEGER NOT NULL DEFAULT 0,
"processingSampleCount" INTEGER NOT NULL DEFAULT 0,
"unsubscribeSampleCount" INTEGER NOT NULL DEFAULT 0,
"routingSampleCount" INTEGER NOT NULL DEFAULT 0,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "TenantProviderMetric_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "TenantProviderMetric_tenantId_provider_key" ON "TenantProviderMetric"("tenantId", "provider");
CREATE INDEX "TenantProviderMetric_tenantId_idx" ON "TenantProviderMetric"("tenantId");
ALTER TABLE "TenantProviderMetric"
ADD CONSTRAINT "TenantProviderMetric_tenantId_fkey"
FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,4 @@
ALTER TABLE "TenantProviderMetric" ADD COLUMN "avgListingSecondsPerMessage" DOUBLE PRECISION;
ALTER TABLE "TenantProviderMetric" ADD COLUMN "avgProcessingSecondsPerMessage" DOUBLE PRECISION;
ALTER TABLE "TenantProviderMetric" ADD COLUMN "avgUnsubscribeSecondsPerMessage" DOUBLE PRECISION;
ALTER TABLE "TenantProviderMetric" ADD COLUMN "avgRoutingSecondsPerMessage" DOUBLE PRECISION;

View File

@@ -0,0 +1,33 @@
-- CreateTable
CREATE TABLE "CleanupJobCandidate" (
"id" TEXT NOT NULL,
"jobId" TEXT NOT NULL,
"mailboxAccountId" TEXT NOT NULL,
"provider" "MailProvider" NOT NULL,
"externalId" TEXT NOT NULL,
"subject" TEXT,
"from" TEXT,
"fromDomain" TEXT,
"listId" TEXT,
"listUnsubscribe" TEXT,
"score" INTEGER NOT NULL,
"signals" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CleanupJobCandidate_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "CleanupJobCandidate_jobId_externalId_key" ON "CleanupJobCandidate"("jobId", "externalId");
-- CreateIndex
CREATE INDEX "CleanupJobCandidate_jobId_idx" ON "CleanupJobCandidate"("jobId");
-- CreateIndex
CREATE INDEX "CleanupJobCandidate_jobId_fromDomain_idx" ON "CleanupJobCandidate"("jobId", "fromDomain");
-- AddForeignKey
ALTER TABLE "CleanupJobCandidate" ADD CONSTRAINT "CleanupJobCandidate_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "CleanupJob"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CleanupJobCandidate" ADD CONSTRAINT "CleanupJobCandidate_mailboxAccountId_fkey" FOREIGN KEY ("mailboxAccountId") REFERENCES "MailboxAccount"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,7 @@
-- AlterTable
ALTER TABLE "CleanupJobCandidate"
ADD COLUMN "receivedAt" TIMESTAMP(3),
ADD COLUMN "actions" JSONB,
ADD COLUMN "unsubscribeStatus" TEXT,
ADD COLUMN "unsubscribeMessage" TEXT,
ADD COLUMN "unsubscribeTarget" TEXT;

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "UnsubscribeAttempt" ADD COLUMN "dedupeKey" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "UnsubscribeAttempt_jobId_dedupeKey_key" ON "UnsubscribeAttempt"("jobId", "dedupeKey");

View File

@@ -0,0 +1,6 @@
-- AlterEnum
ALTER TYPE "RuleActionType" ADD VALUE IF NOT EXISTS 'MARK_READ';
ALTER TYPE "RuleActionType" ADD VALUE IF NOT EXISTS 'MARK_UNREAD';
-- AlterEnum
ALTER TYPE "RuleConditionType" ADD VALUE IF NOT EXISTS 'UNSUBSCRIBE_STATUS';

View File

@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "RuleConditionType" ADD VALUE IF NOT EXISTS 'SCORE';

View File

@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "UnsubscribeHistory" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"dedupeKey" TEXT NOT NULL,
"target" TEXT NOT NULL,
"status" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "UnsubscribeHistory_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "UnsubscribeHistory_tenantId_dedupeKey_key" ON "UnsubscribeHistory"("tenantId", "dedupeKey");
-- CreateIndex
CREATE INDEX "UnsubscribeHistory_tenantId_idx" ON "UnsubscribeHistory"("tenantId");

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "CleanupJobCandidate" ADD COLUMN "reviewed" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "RuleMatchMode" AS ENUM ('ALL', 'ANY');
-- AlterTable
ALTER TABLE "Rule" ADD COLUMN "matchMode" "RuleMatchMode" NOT NULL DEFAULT 'ALL';

View File

@@ -31,14 +31,19 @@ enum RuleActionType {
DELETE
ARCHIVE
LABEL
MARK_READ
MARK_UNREAD
}
enum RuleConditionType {
HEADER
HEADER_MISSING
SUBJECT
FROM
LIST_UNSUBSCRIBE
LIST_ID
UNSUBSCRIBE_STATUS
SCORE
}
enum ExportStatus {
@@ -60,6 +65,42 @@ model Tenant {
mailboxAccounts MailboxAccount[]
rules Rule[]
jobs CleanupJob[]
metric TenantMetric?
providerMetrics TenantProviderMetric[]
}
model TenantMetric {
id String @id @default(cuid())
tenantId String @unique
avgProcessingRate Float?
sampleCount Int @default(0)
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
}
model TenantProviderMetric {
id String @id @default(cuid())
tenantId String
provider MailProvider
avgListingRate Float?
avgProcessingRate Float?
avgUnsubscribeRate Float?
avgRoutingRate Float?
avgListingSecondsPerMessage Float?
avgProcessingSecondsPerMessage Float?
avgUnsubscribeSecondsPerMessage Float?
avgRoutingSecondsPerMessage Float?
listingSampleCount Int @default(0)
processingSampleCount Int @default(0)
unsubscribeSampleCount Int @default(0)
routingSampleCount Int @default(0)
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([tenantId, provider])
@@index([tenantId])
}
model ExportJob {
@@ -120,6 +161,7 @@ model MailboxAccount {
tenant Tenant @relation(fields: [tenantId], references: [id])
folders MailboxFolder[]
jobs CleanupJob[]
candidates CleanupJobCandidate[]
@@index([tenantId])
}
@@ -161,6 +203,9 @@ model Rule {
tenantId String
name String
enabled Boolean @default(true)
matchMode RuleMatchMode @default(ALL)
position Int @default(0)
stopOnMatch Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -169,6 +214,12 @@ model Rule {
actions RuleAction[]
@@index([tenantId])
@@index([tenantId, position])
}
enum RuleMatchMode {
ALL
ANY
}
model RuleCondition {
@@ -205,6 +256,12 @@ model CleanupJob {
checkpointUpdatedAt DateTime?
processedMessages Int?
totalMessages Int?
listingSeconds Int?
processingSeconds Int?
unsubscribeSeconds Int?
routingSeconds Int?
unsubscribeAttempts Int?
actionAttempts Int?
startedAt DateTime?
finishedAt DateTime?
createdAt DateTime @default(now())
@@ -212,17 +269,50 @@ model CleanupJob {
tenant Tenant @relation(fields: [tenantId], references: [id])
mailboxAccount MailboxAccount @relation(fields: [mailboxAccountId], references: [id])
unsubscribeAttempts UnsubscribeAttempt[]
unsubscribeAttemptItems UnsubscribeAttempt[]
events CleanupJobEvent[]
candidates CleanupJobCandidate[]
@@index([tenantId])
@@index([mailboxAccountId])
}
model CleanupJobCandidate {
id String @id @default(cuid())
jobId String
mailboxAccountId String
provider MailProvider
externalId String
subject String?
from String?
fromDomain String?
receivedAt DateTime?
listId String?
listUnsubscribe String?
listUnsubscribePost String?
score Int
signals Json
actions Json?
unsubscribeStatus String?
unsubscribeMessage String?
unsubscribeTarget String?
unsubscribeDetails Json?
reviewed Boolean @default(false)
createdAt DateTime @default(now())
job CleanupJob @relation(fields: [jobId], references: [id])
mailboxAccount MailboxAccount @relation(fields: [mailboxAccountId], references: [id])
@@unique([jobId, externalId])
@@index([jobId])
@@index([jobId, fromDomain])
}
model UnsubscribeAttempt {
id String @id @default(cuid())
jobId String
mailItemId String?
dedupeKey String?
method String
target String
status String
@@ -231,6 +321,19 @@ model UnsubscribeAttempt {
job CleanupJob @relation(fields: [jobId], references: [id])
@@index([jobId])
@@unique([jobId, dedupeKey])
}
model UnsubscribeHistory {
id String @id @default(cuid())
tenantId String
dedupeKey String
target String
status String
createdAt DateTime @default(now())
@@unique([tenantId, dedupeKey])
@@index([tenantId])
}
model CleanupJobEvent {