feat: T4 wave 3 - notifications per user, suggestions API, varroa bands, split/queen events
- Schema III: per-user notifications (broadcast fan-out migration, category, link, requiredPermissions filter), notification preferences and settings endpoints, calendar withdrawal switch, occupiedFrames - Suggestions API: list/summary with permission visibility and paging, accept/dismiss/reopen incl. bulk, SuggestionView on all responses - Varroa traffic light by season and method, young colonies, treatment effect window, sample-based percentage; shared withdrawal-window helper on Berlin calendar days; GET /varroa-measurements/thresholds - ColonySplit and QueenAssigned domain events with calendar and webhook handlers - Outbox claims compare against now() rounded to timestamp(3); fixes the flaky e2e claims of rows written with SQL now() Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
d99a697739
commit
f468dc5f75
87 files changed
+6022
-510
No files matched your search
@@ -0,0 +1,33 @@
|
||||
-- T4 migration 8/n: every notification belongs to exactly one user. A
|
||||
-- broadcast (userId IS NULL) is copied once per active user with its read
|
||||
-- flag and creation time, then the original is deleted. After that userId is
|
||||
-- NOT NULL; dedupeKey ("<prefix>:<userId>") makes repeated writes harmless.
|
||||
-- Check afterwards: SELECT count(*) FROM "Notification" WHERE "userId" IS NULL;
|
||||
-- must be 0 (the SET NOT NULL below fails otherwise).
|
||||
|
||||
-- DropForeignKey (was ON DELETE SET NULL, impossible for a NOT NULL column)
|
||||
ALTER TABLE "Notification" DROP CONSTRAINT "Notification_userId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Notification" ADD COLUMN "dedupeKey" TEXT;
|
||||
|
||||
-- Fan-out of the broadcasts to ALL active users (isActive only, no role or
|
||||
-- permission filter): a broadcast was visible to every user before, so the
|
||||
-- copies keep exactly that visibility. With no active user, broadcasts are
|
||||
-- dropped. The copies get no dedupeKey and the default requiredPermissions.
|
||||
INSERT INTO "Notification" ("id", "userId", "priority", "message", "relatedType", "relatedId", "read", "createdAt")
|
||||
SELECT gen_random_uuid()::text, u."id", n."priority", n."message", n."relatedType", n."relatedId", n."read", n."createdAt"
|
||||
FROM "Notification" n
|
||||
CROSS JOIN "User" u
|
||||
WHERE n."userId" IS NULL AND u."isActive" = true;
|
||||
|
||||
DELETE FROM "Notification" WHERE "userId" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Notification" ALTER COLUMN "userId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Notification_dedupeKey_key" ON "Notification"("dedupeKey");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- T4 migration 9/n: switch for the withdrawal-period entries of the calendar
|
||||
-- sync (on by default, like the other sources).
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "CalendarSyncSettings" ADD COLUMN "syncWithdrawal" BOOLEAN NOT NULL DEFAULT true;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- T4 migration 10/n: Colony.occupiedFrames, the number of occupied frames
|
||||
-- (0-40, same unit and bounds as Inspection.colonyStrength). Colony.strength
|
||||
-- (scale 1-10) is left untouched, nothing is converted.
|
||||
--
|
||||
-- Check BEFORE the deploy (hits must be clarified with the owner, never cut):
|
||||
-- SELECT count(*) FROM "Inspection" WHERE "colonyStrength" > 40;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Colony" ADD COLUMN "occupiedFrames" INTEGER;
|
||||
|
||||
ALTER TABLE "Colony" ADD CONSTRAINT "Colony_occupiedFrames_range" CHECK ("occupiedFrames" BETWEEN 0 AND 40);
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
-- T4 migration 11/n (part category, link, permissions, preferences). Runs
|
||||
-- after migration 8. category of the old rows: job failures carry
|
||||
-- relatedType 'JobRun' (and rows without relatedType are system messages) ->
|
||||
-- SYSTEM, everything else -> SUGGESTION. Only then NOT NULL.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationCategory" AS ENUM ('TASK_DUE', 'DIGEST', 'SUGGESTION', 'WITHDRAWAL', 'SEASON', 'SYSTEM');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Notification" ADD COLUMN "category" "NotificationCategory",
|
||||
ADD COLUMN "link" TEXT,
|
||||
ADD COLUMN "requiredPermissions" TEXT[] DEFAULT ARRAY[]::TEXT[];
|
||||
|
||||
UPDATE "Notification"
|
||||
SET "category" = CASE
|
||||
WHEN "relatedType" = 'JobRun' OR "relatedType" IS NULL THEN 'SYSTEM'::"NotificationCategory"
|
||||
ELSE 'SUGGESTION'::"NotificationCategory"
|
||||
END;
|
||||
|
||||
ALTER TABLE "Notification" ALTER COLUMN "category" SET NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationPreference" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"taskDueEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"digestEnabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"digestTime" TEXT NOT NULL DEFAULT '06:30',
|
||||
"quietFrom" TEXT DEFAULT '21:00',
|
||||
"quietUntil" TEXT DEFAULT '06:30',
|
||||
"telegramEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"telegramCategories" "NotificationCategory"[] DEFAULT ARRAY['TASK_DUE', 'DIGEST']::"NotificationCategory"[],
|
||||
"telegramDetail" BOOLEAN NOT NULL DEFAULT true,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "NotificationPreference_pkey" PRIMARY KEY ("userId")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationPreference" ADD CONSTRAINT "NotificationPreference_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
Reference in new issue
Block a user