feat: T4b/T4c wave 8 - weather history, phenology, honey chain, Telegram base
- Weather history from Open-Meteo behind an adapter: worker loop phenology-weather with backoff, Retry-After, User-Agent, chunked writes and forecast-to-archive correction - Phenology: growing degree sum per DWD, phase from reports, GTS and calendar, calibration from previous years, daily job step, SeasonService reads the stored state - Honey chain: prefill for extractions and batches (proposals only), plausibility and withdrawal warnings, Extraction lock, chain events and batch.created webhook from HoneyBatchRecorded - Telegram base: schema, NotificationDelivery outbox, hardened bot client, startup check without crash, no-network-outside-loops guard - ARCHITECTURE.md: sections 11.11-11.13, assumptions 42-46, env table Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
22382d53a2
commit
1971b2d9c4
90 files changed
+8431
-155
No files matched your search
@@ -0,0 +1,111 @@
|
||||
-- T4 migration 11/n (part Telegram and delivery): chat binding, single-use
|
||||
-- link tokens, the delivery outbox and the receiver's poll state. All four
|
||||
-- tables are excluded from the backup (MODELS_EXCLUDED_FROM_BACKUP).
|
||||
-- NotificationDelivery carries either a notification for a user or a bot
|
||||
-- system reply for a chat, never both (CHECK below), and at most one
|
||||
-- delivery per notification and channel. TelegramPollState holds exactly one
|
||||
-- row (CHECK below), created here.
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationChannel" AS ENUM ('TELEGRAM');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "NotificationDeliveryStatus" AS ENUM ('PENDING', 'PROCESSING', 'DELIVERED', 'SKIPPED', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TelegramLink" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"chatId" BIGINT NOT NULL,
|
||||
"telegramFirstName" TEXT,
|
||||
"linkedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TelegramLink_pkey" PRIMARY KEY ("userId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TelegramLinkToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"tokenHash" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"consumedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "TelegramLinkToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "NotificationDelivery" (
|
||||
"id" TEXT NOT NULL,
|
||||
"channel" "NotificationChannel" NOT NULL,
|
||||
"userId" TEXT,
|
||||
"chatId" BIGINT,
|
||||
"notificationId" TEXT,
|
||||
"templateCode" TEXT,
|
||||
"status" "NotificationDeliveryStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"attempts" INTEGER NOT NULL DEFAULT 0,
|
||||
"availableAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"claimedAt" TIMESTAMP(3),
|
||||
"lastStatusCode" INTEGER,
|
||||
"lastError" TEXT,
|
||||
"deliveredAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "NotificationDelivery_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "TelegramPollState" (
|
||||
"id" TEXT NOT NULL DEFAULT 'singleton',
|
||||
"updateOffset" BIGINT NOT NULL DEFAULT 0,
|
||||
"leaseOwner" TEXT,
|
||||
"leaseUntil" TIMESTAMP(3),
|
||||
"lastPollAt" TIMESTAMP(3),
|
||||
"lastError" TEXT,
|
||||
|
||||
CONSTRAINT "TelegramPollState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TelegramLink_chatId_key" ON "TelegramLink"("chatId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "TelegramLinkToken_tokenHash_key" ON "TelegramLinkToken"("tokenHash");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "TelegramLinkToken_userId_idx" ON "TelegramLinkToken"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NotificationDelivery_status_availableAt_idx" ON "NotificationDelivery"("status", "availableAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NotificationDelivery_userId_createdAt_idx" ON "NotificationDelivery"("userId", "createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "NotificationDelivery_notificationId_idx" ON "NotificationDelivery"("notificationId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "NotificationDelivery_notificationId_channel_key" ON "NotificationDelivery"("notificationId", "channel");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TelegramLink" ADD CONSTRAINT "TelegramLink_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "TelegramLinkToken" ADD CONSTRAINT "TelegramLinkToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationDelivery" ADD CONSTRAINT "NotificationDelivery_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "NotificationDelivery" ADD CONSTRAINT "NotificationDelivery_notificationId_fkey" FOREIGN KEY ("notificationId") REFERENCES "Notification"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- A delivery is either a notification for a user (rendered at send time) or a
|
||||
-- system reply for a chat (rendered from templateCode).
|
||||
ALTER TABLE "NotificationDelivery" ADD CONSTRAINT "NotificationDelivery_target_check" CHECK (
|
||||
("notificationId" IS NOT NULL AND "userId" IS NOT NULL AND "templateCode" IS NULL AND "chatId" IS NULL)
|
||||
OR ("notificationId" IS NULL AND "templateCode" IS NOT NULL AND "chatId" IS NOT NULL)
|
||||
);
|
||||
|
||||
-- The receiver's single state row.
|
||||
ALTER TABLE "TelegramPollState" ADD CONSTRAINT "TelegramPollState_singleton_check" CHECK ("id" = 'singleton');
|
||||
INSERT INTO "TelegramPollState" ("id") VALUES ('singleton');
|
||||
Reference in new issue
Block a user