fix notifications
Build & Push / Pipeline Tests (push) Failing after 1m8s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 52s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Successful in 46s
Test / API Integration Tests (push) Successful in 1m7s
Build & Push / Pipeline Tests (push) Failing after 1m8s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 53s
Test / API Unit Tests (push) Failing after 52s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Carplace Unit Tests (push) Successful in 43s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Successful in 46s
Test / API Integration Tests (push) Successful in 1m7s
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
-- Durable, recipient-safe notification model. The legacy notifications table is
|
||||
-- intentionally retained for rollback and audit during the migration window.
|
||||
|
||||
CREATE TYPE "NotificationRecipientType" AS ENUM ('EMPLOYEE', 'RENTER');
|
||||
CREATE TYPE "NotificationDeliveryStatus" AS ENUM ('PENDING', 'QUEUED', 'SENT', 'DELIVERED', 'FAILED', 'SKIPPED', 'DEAD_LETTER');
|
||||
CREATE TYPE "NotificationOutboxStatus" AS ENUM ('PENDING', 'PUBLISHED', 'FAILED');
|
||||
|
||||
CREATE TABLE "notification_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"type" "NotificationType" NOT NULL,
|
||||
"templateKey" TEXT,
|
||||
"locale" TEXT NOT NULL DEFAULT 'en',
|
||||
"title" TEXT NOT NULL,
|
||||
"body" TEXT NOT NULL,
|
||||
"data" JSONB,
|
||||
"sourceType" TEXT NOT NULL,
|
||||
"sourceId" TEXT NOT NULL,
|
||||
"idempotencyKey" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notification_events_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "notification_events_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE "notification_recipients" (
|
||||
"id" TEXT NOT NULL,
|
||||
"notificationEventId" TEXT NOT NULL,
|
||||
"recipientType" "NotificationRecipientType" NOT NULL,
|
||||
"employeeId" TEXT,
|
||||
"renterId" TEXT,
|
||||
"readAt" TIMESTAMP(3),
|
||||
"archivedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notification_recipients_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "notification_recipients_notificationEventId_fkey" FOREIGN KEY ("notificationEventId") REFERENCES "notification_events"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "notification_recipients_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "employees"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "notification_recipients_renterId_fkey" FOREIGN KEY ("renterId") REFERENCES "renters"("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "notification_recipients_exactly_one_identity_chk" CHECK (
|
||||
(CASE WHEN "employeeId" IS NULL THEN 0 ELSE 1 END) +
|
||||
(CASE WHEN "renterId" IS NULL THEN 0 ELSE 1 END) = 1
|
||||
),
|
||||
CONSTRAINT "notification_recipients_type_identity_chk" CHECK (
|
||||
("recipientType" = 'EMPLOYEE' AND "employeeId" IS NOT NULL AND "renterId" IS NULL) OR
|
||||
("recipientType" = 'RENTER' AND "renterId" IS NOT NULL AND "employeeId" IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TABLE "notification_deliveries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"notificationRecipientId" TEXT NOT NULL,
|
||||
"channel" "NotificationChannel" NOT NULL,
|
||||
"status" "NotificationDeliveryStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"attemptCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastAttemptAt" TIMESTAMP(3),
|
||||
"nextAttemptAt" TIMESTAMP(3),
|
||||
"provider" TEXT,
|
||||
"providerMessageId" TEXT,
|
||||
"failureCode" TEXT,
|
||||
"failureReason" TEXT,
|
||||
"preferenceDecision" TEXT,
|
||||
"sentAt" TIMESTAMP(3),
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notification_deliveries_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "notification_deliveries_notificationRecipientId_fkey" FOREIGN KEY ("notificationRecipientId") REFERENCES "notification_recipients"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE "notification_outbox" (
|
||||
"id" TEXT NOT NULL,
|
||||
"notificationEventId" TEXT NOT NULL,
|
||||
"status" "NotificationOutboxStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"payload" JSONB NOT NULL,
|
||||
"publishedAt" TIMESTAMP(3),
|
||||
"failureReason" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "notification_outbox_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "notification_outbox_notificationEventId_fkey" FOREIGN KEY ("notificationEventId") REFERENCES "notification_events"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE "company_notification_preferences" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"notificationType" "NotificationType" NOT NULL,
|
||||
"channel" "NotificationChannel" NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "company_notification_preferences_pkey" PRIMARY KEY ("id"),
|
||||
CONSTRAINT "company_notification_preferences_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX "notification_events_companyId_idempotencyKey_key" ON "notification_events"("companyId", "idempotencyKey");
|
||||
CREATE INDEX "notification_events_companyId_type_createdAt_idx" ON "notification_events"("companyId", "type", "createdAt");
|
||||
CREATE INDEX "notification_recipients_employeeId_readAt_createdAt_idx" ON "notification_recipients"("employeeId", "readAt", "createdAt");
|
||||
CREATE INDEX "notification_recipients_renterId_readAt_createdAt_idx" ON "notification_recipients"("renterId", "readAt", "createdAt");
|
||||
CREATE INDEX "notification_recipients_notificationEventId_idx" ON "notification_recipients"("notificationEventId");
|
||||
CREATE UNIQUE INDEX "notification_deliveries_notificationRecipientId_channel_key" ON "notification_deliveries"("notificationRecipientId", "channel");
|
||||
CREATE INDEX "notification_deliveries_channel_status_nextAttemptAt_idx" ON "notification_deliveries"("channel", "status", "nextAttemptAt");
|
||||
CREATE INDEX "notification_outbox_status_createdAt_idx" ON "notification_outbox"("status", "createdAt");
|
||||
CREATE UNIQUE INDEX "company_notification_preferences_companyId_notificationType_channel_key" ON "company_notification_preferences"("companyId", "notificationType", "channel");
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
WITH notification_menu AS (
|
||||
INSERT INTO "menu_items" (
|
||||
"id",
|
||||
"systemKey",
|
||||
"label",
|
||||
"itemType",
|
||||
"routeOrUrl",
|
||||
"icon",
|
||||
"displayOrder",
|
||||
"openInNewTab",
|
||||
"isRequired",
|
||||
"isActive",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"createdBy",
|
||||
"updatedBy"
|
||||
)
|
||||
VALUES (
|
||||
'menu_notifications',
|
||||
'notifications',
|
||||
'Notifications',
|
||||
'INTERNAL_PAGE'::"MenuItemType",
|
||||
'/notifications',
|
||||
'Bell',
|
||||
120,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
'system',
|
||||
'system'
|
||||
)
|
||||
ON CONFLICT ("systemKey") DO UPDATE
|
||||
SET
|
||||
"label" = EXCLUDED."label",
|
||||
"itemType" = EXCLUDED."itemType",
|
||||
"routeOrUrl" = EXCLUDED."routeOrUrl",
|
||||
"icon" = EXCLUDED."icon",
|
||||
"isActive" = true,
|
||||
"updatedBy" = 'system',
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
RETURNING "id"
|
||||
),
|
||||
notification_plan_assignments ("plan", "displayOrder") AS (
|
||||
VALUES
|
||||
('STARTER'::"Plan", 120),
|
||||
('GROWTH'::"Plan", 120),
|
||||
('PRO'::"Plan", 120)
|
||||
)
|
||||
INSERT INTO "subscription_menu_items" (
|
||||
"id",
|
||||
"plan",
|
||||
"menuItemId",
|
||||
"displayOrder",
|
||||
"isActive",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'menu_sub_' || lower(npa."plan"::text) || '_notifications',
|
||||
npa."plan",
|
||||
notification_menu."id",
|
||||
npa."displayOrder",
|
||||
true,
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM notification_plan_assignments npa
|
||||
CROSS JOIN notification_menu
|
||||
ON CONFLICT ("plan", "menuItemId") DO UPDATE
|
||||
SET
|
||||
"displayOrder" = EXCLUDED."displayOrder",
|
||||
"isActive" = true,
|
||||
"updatedAt" = CURRENT_TIMESTAMP;
|
||||
|
||||
WITH notification_menu AS (
|
||||
SELECT "id" FROM "menu_items" WHERE "systemKey" = 'notifications'
|
||||
),
|
||||
notification_roles ("role") AS (
|
||||
VALUES
|
||||
('OWNER'::"EmployeeRole"),
|
||||
('MANAGER'::"EmployeeRole"),
|
||||
('AGENT'::"EmployeeRole")
|
||||
)
|
||||
INSERT INTO "menu_item_role_visibility" ("id", "menuItemId", "role", "createdAt", "updatedAt")
|
||||
SELECT
|
||||
'menu_role_' || lower(notification_roles."role"::text) || '_notifications',
|
||||
notification_menu."id",
|
||||
notification_roles."role",
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM notification_roles
|
||||
CROSS JOIN notification_menu
|
||||
ON CONFLICT ("menuItemId", "role") DO NOTHING;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
WITH notification_feature ("plan", "sortOrder") AS (
|
||||
VALUES
|
||||
('STARTER'::"Plan", 50),
|
||||
('GROWTH'::"Plan", 150),
|
||||
('PRO'::"Plan", 150)
|
||||
)
|
||||
INSERT INTO "plan_features" (
|
||||
"id",
|
||||
"plan",
|
||||
"label",
|
||||
"sortOrder",
|
||||
"createdAt",
|
||||
"updatedAt"
|
||||
)
|
||||
SELECT
|
||||
'plan_feature_' || lower(notification_feature."plan"::text) || '_notification_management',
|
||||
notification_feature."plan",
|
||||
'NOTIFICATION_MANAGEMENT',
|
||||
notification_feature."sortOrder",
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM notification_feature
|
||||
ON CONFLICT ("id") DO UPDATE
|
||||
SET
|
||||
"label" = EXCLUDED."label",
|
||||
"sortOrder" = EXCLUDED."sortOrder",
|
||||
"updatedAt" = CURRENT_TIMESTAMP;
|
||||
Reference in New Issue
Block a user