feat: fix channel scanning bugs, add package tags, and kickstarters tab

Bug fixes:
- Fix channels not being scanned by paginating TDLib getChats (was only
  loading first batch, additional channels were unknown to TDLib)
- Add per-channel getChat pre-load as safety net before scanning
- Fix preview pictures not loading by checking previewData instead of
  previewMsgId for hasPreview flag
- Prevent previewMsgId from being set when preview download fails

Package Tags:
- Add tags Text[] column to Package with migration backfilling from
  channel categories
- Worker auto-inherits source channel category as initial tag
- Tag filter dropdown and Tags column in STL Files table
- Server actions for individual and bulk tag editing

Kickstarters Tab:
- New KickstarterHost, Kickstarter, and KickstarterPackage models
- Full CRUD with delivery status, payment status, host management
- Package linking (many-to-many with existing packages)
- Sidebar entry with Gift icon
- Table with search, filters, modal forms

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 18:17:44 +01:00
parent e2dd3bb9d0
commit 5fd341dfc4
23 changed files with 1375 additions and 32 deletions

View File

@@ -38,6 +38,7 @@ model User {
tags Tag[]
settings UserSettings?
telegramLink TelegramLink?
kickstarters Kickstarter[]
inviteCodes InviteCode[] @relation("InviteCreator")
usedInvite InviteCode? @relation("InviteUser", fields: [usedInviteId], references: [id], onDelete: SetNull)
usedInviteId String?
@@ -468,6 +469,7 @@ model Package {
isMultipart Boolean @default(false)
partCount Int @default(1)
fileCount Int @default(0)
tags String[] @default([])
previewData Bytes? // JPEG thumbnail from nearby Telegram photo (stored as raw bytes)
previewMsgId BigInt? // Telegram message ID of the matched photo
indexedAt DateTime @default(now())
@@ -477,8 +479,9 @@ model Package {
files PackageFile[]
ingestionRun IngestionRun? @relation(fields: [ingestionRunId], references: [id])
ingestionRunId String?
sendRequests BotSendRequest[]
extractRequests ArchiveExtractRequest[]
sendRequests BotSendRequest[]
extractRequests ArchiveExtractRequest[]
kickstarterLinks KickstarterPackage[]
@@index([sourceChannelId])
@@index([destChannelId])
@@ -682,3 +685,63 @@ model ArchiveExtractRequest {
@@index([status])
@@map("archive_extract_requests")
}
// ───────────────────────────────────────
// Purchased Kickstarters
// ───────────────────────────────────────
enum DeliveryStatus {
NOT_DELIVERED
PARTIAL
DELIVERED
}
enum PaymentStatus {
PAID
UNPAID
}
model KickstarterHost {
id String @id @default(cuid())
name String @unique
createdAt DateTime @default(now())
kickstarters Kickstarter[]
@@map("kickstarter_hosts")
}
model Kickstarter {
id String @id @default(cuid())
name String
link String?
filesUrl String?
deliveryStatus DeliveryStatus @default(NOT_DELIVERED)
paymentStatus PaymentStatus @default(UNPAID)
notes String?
hostId String?
userId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
host KickstarterHost? @relation(fields: [hostId], references: [id], onDelete: SetNull)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
packages KickstarterPackage[]
@@index([hostId])
@@index([userId])
@@index([deliveryStatus])
@@index([paymentStatus])
@@map("kickstarters")
}
model KickstarterPackage {
kickstarterId String
packageId String
kickstarter Kickstarter @relation(fields: [kickstarterId], references: [id], onDelete: Cascade)
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
@@id([kickstarterId, packageId])
@@map("kickstarter_packages")
}