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
@@ -127,6 +127,9 @@ model User {
|
||||
rawMaterialLots RawMaterialLot[]
|
||||
notifications Notification[]
|
||||
notificationPreference NotificationPreference?
|
||||
telegramLink TelegramLink?
|
||||
telegramLinkTokens TelegramLinkToken[]
|
||||
notificationDeliveries NotificationDelivery[]
|
||||
queenEvaluations QueenEvaluation[]
|
||||
webhookSubscriptions WebhookSubscription[]
|
||||
documents Document[]
|
||||
@@ -1408,19 +1411,20 @@ enum NotificationCategory {
|
||||
// beim Schreiben). requiredPermissions = Rechte des Empfaengers plus Rechte
|
||||
// des Inhalts; dedupeKey = "<praefix>:<userId>".
|
||||
model Notification {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
priority NotificationPriority
|
||||
category NotificationCategory
|
||||
message String
|
||||
relatedType String?
|
||||
relatedId String?
|
||||
link String?
|
||||
requiredPermissions String[] @default([])
|
||||
dedupeKey String? @unique
|
||||
read Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
requiredPermissions String[] @default([])
|
||||
dedupeKey String? @unique
|
||||
read Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
deliveries NotificationDelivery[]
|
||||
|
||||
@@index([userId, read])
|
||||
@@index([priority])
|
||||
@@ -1441,6 +1445,78 @@ model NotificationPreference {
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
enum NotificationChannel {
|
||||
TELEGRAM
|
||||
}
|
||||
|
||||
enum NotificationDeliveryStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
DELIVERED
|
||||
SKIPPED
|
||||
FAILED
|
||||
}
|
||||
|
||||
// Chat binding of a user to this deployment's Telegram bot (T4c). One private
|
||||
// chat belongs to exactly one user.
|
||||
model TelegramLink {
|
||||
userId String @id
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
chatId BigInt @unique
|
||||
telegramFirstName String?
|
||||
linkedAt DateTime @default(now())
|
||||
}
|
||||
|
||||
// Single-use link token, valid for 10 minutes; only the SHA-256 is stored.
|
||||
model TelegramLinkToken {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
tokenHash String @unique
|
||||
expiresAt DateTime
|
||||
consumedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
// Outbox like WebhookDelivery. The text is not stored: the sender renders it
|
||||
// from the Notification or the templateCode. CHECK (migration): exactly one of
|
||||
// (notificationId with userId) or (templateCode with chatId).
|
||||
model NotificationDelivery {
|
||||
id String @id @default(cuid())
|
||||
channel NotificationChannel
|
||||
userId String?
|
||||
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
chatId BigInt?
|
||||
notificationId String?
|
||||
notification Notification? @relation(fields: [notificationId], references: [id], onDelete: Cascade)
|
||||
templateCode String?
|
||||
status NotificationDeliveryStatus @default(PENDING)
|
||||
attempts Int @default(0)
|
||||
availableAt DateTime @default(now())
|
||||
claimedAt DateTime?
|
||||
lastStatusCode Int?
|
||||
lastError String?
|
||||
deliveredAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([notificationId, channel])
|
||||
@@index([status, availableAt])
|
||||
@@index([userId, createdAt])
|
||||
@@index([notificationId])
|
||||
}
|
||||
|
||||
// One row: update offset and poll lease of the Telegram receiver.
|
||||
model TelegramPollState {
|
||||
id String @id @default("singleton")
|
||||
updateOffset BigInt @default(0)
|
||||
leaseOwner String?
|
||||
leaseUntil DateTime?
|
||||
lastPollAt DateTime?
|
||||
lastError String?
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Webhooks (Abschnitt 27) – v1.x Modul 28
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user