mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
- Add 7Z and DOCUMENT to ArchiveType enum - Detect .7z, .pdf, .stl, .obj, .3mf, .step, .blend, .gcode, .svg, .dxf, .ai, .eps, .psd files as fetchable documents - Handle DOCUMENT and 7Z formats in worker pipeline (skip extraction, record file as single entry) - Add Prisma migration for new enum values
647 lines
18 KiB
Plaintext
647 lines
18 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
}
|
|
|
|
// ───────────────────────────────────────
|
|
// Auth.js required models
|
|
// ───────────────────────────────────────
|
|
|
|
enum Role {
|
|
ADMIN
|
|
USER
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
name String?
|
|
email String? @unique
|
|
emailVerified DateTime?
|
|
image String?
|
|
hashedPassword String?
|
|
role Role @default(ADMIN)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
accounts Account[]
|
|
sessions Session[]
|
|
filaments Filament[]
|
|
resins Resin[]
|
|
paints Paint[]
|
|
supplies Supply[]
|
|
vendors Vendor[]
|
|
locations Location[]
|
|
usageLogs UsageLog[]
|
|
tags Tag[]
|
|
settings UserSettings?
|
|
telegramLink TelegramLink?
|
|
inviteCodes InviteCode[]
|
|
}
|
|
|
|
model Account {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
type String
|
|
provider String
|
|
providerAccountId String
|
|
refresh_token String? @db.Text
|
|
access_token String? @db.Text
|
|
expires_at Int?
|
|
token_type String?
|
|
scope String?
|
|
id_token String? @db.Text
|
|
session_state String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([provider, providerAccountId])
|
|
}
|
|
|
|
model Session {
|
|
id String @id @default(cuid())
|
|
sessionToken String @unique
|
|
userId String
|
|
expires DateTime
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
model VerificationToken {
|
|
identifier String
|
|
token String
|
|
expires DateTime
|
|
|
|
@@unique([identifier, token])
|
|
}
|
|
|
|
// ───────────────────────────────────────
|
|
// Domain models
|
|
// ───────────────────────────────────────
|
|
|
|
model Vendor {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(64)
|
|
website String? @db.VarChar(256)
|
|
notes String? @db.Text
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
userId String
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
filaments Filament[]
|
|
resins Resin[]
|
|
paints Paint[]
|
|
supplies Supply[]
|
|
|
|
@@index([userId])
|
|
@@index([archived])
|
|
}
|
|
|
|
model Location {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(64)
|
|
description String? @db.VarChar(256)
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
userId String
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
filaments Filament[]
|
|
resins Resin[]
|
|
paints Paint[]
|
|
supplies Supply[]
|
|
|
|
@@index([userId])
|
|
@@index([archived])
|
|
}
|
|
|
|
model Filament {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(128)
|
|
brand String @db.VarChar(64)
|
|
material String @db.VarChar(32)
|
|
color String @db.VarChar(64)
|
|
colorHex String @db.VarChar(7)
|
|
diameter Float @default(1.75)
|
|
spoolWeight Float
|
|
usedWeight Float @default(0)
|
|
emptySpoolWeight Float @default(0)
|
|
purchaseDate DateTime?
|
|
cost Float?
|
|
notes String? @db.Text
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userId String
|
|
vendorId String?
|
|
locationId String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
|
|
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
|
|
tags TagOnFilament[]
|
|
usageLogs UsageLog[]
|
|
|
|
@@index([userId])
|
|
@@index([vendorId])
|
|
@@index([locationId])
|
|
@@index([material])
|
|
@@index([archived])
|
|
@@index([brand])
|
|
}
|
|
|
|
model Resin {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(128)
|
|
brand String @db.VarChar(64)
|
|
resinType String @db.VarChar(32)
|
|
color String @db.VarChar(64)
|
|
colorHex String @db.VarChar(7)
|
|
bottleSize Float
|
|
usedML Float @default(0)
|
|
purchaseDate DateTime?
|
|
cost Float?
|
|
notes String? @db.Text
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userId String
|
|
vendorId String?
|
|
locationId String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
|
|
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
|
|
tags TagOnResin[]
|
|
usageLogs UsageLog[]
|
|
|
|
@@index([userId])
|
|
@@index([vendorId])
|
|
@@index([locationId])
|
|
@@index([resinType])
|
|
@@index([archived])
|
|
@@index([brand])
|
|
}
|
|
|
|
model Paint {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(128)
|
|
brand String @db.VarChar(64)
|
|
line String? @db.VarChar(64)
|
|
color String @db.VarChar(64)
|
|
colorHex String @db.VarChar(7)
|
|
finish String @db.VarChar(32)
|
|
volumeML Float
|
|
usedML Float @default(0)
|
|
purchaseDate DateTime?
|
|
cost Float?
|
|
notes String? @db.Text
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userId String
|
|
vendorId String?
|
|
locationId String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
|
|
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
|
|
tags TagOnPaint[]
|
|
usageLogs UsageLog[]
|
|
|
|
@@index([userId])
|
|
@@index([vendorId])
|
|
@@index([locationId])
|
|
@@index([finish])
|
|
@@index([archived])
|
|
@@index([brand])
|
|
}
|
|
|
|
model Tag {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(64)
|
|
createdAt DateTime @default(now())
|
|
userId String
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
filaments TagOnFilament[]
|
|
resins TagOnResin[]
|
|
paints TagOnPaint[]
|
|
supplies TagOnSupply[]
|
|
|
|
@@unique([name, userId])
|
|
@@index([userId])
|
|
}
|
|
|
|
model TagOnFilament {
|
|
filamentId String
|
|
tagId String
|
|
|
|
filament Filament @relation(fields: [filamentId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([filamentId, tagId])
|
|
}
|
|
|
|
model TagOnResin {
|
|
resinId String
|
|
tagId String
|
|
|
|
resin Resin @relation(fields: [resinId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([resinId, tagId])
|
|
}
|
|
|
|
model TagOnPaint {
|
|
paintId String
|
|
tagId String
|
|
|
|
paint Paint @relation(fields: [paintId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([paintId, tagId])
|
|
}
|
|
|
|
model Supply {
|
|
id String @id @default(cuid())
|
|
name String @db.VarChar(128)
|
|
brand String @db.VarChar(64)
|
|
category String @db.VarChar(32)
|
|
color String? @db.VarChar(64)
|
|
colorHex String? @db.VarChar(7)
|
|
totalAmount Float
|
|
usedAmount Float @default(0)
|
|
unit String @db.VarChar(16)
|
|
purchaseDate DateTime?
|
|
cost Float?
|
|
notes String? @db.Text
|
|
archived Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userId String
|
|
vendorId String?
|
|
locationId String?
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
vendor Vendor? @relation(fields: [vendorId], references: [id], onDelete: SetNull)
|
|
location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull)
|
|
tags TagOnSupply[]
|
|
usageLogs UsageLog[]
|
|
|
|
@@index([userId])
|
|
@@index([vendorId])
|
|
@@index([locationId])
|
|
@@index([category])
|
|
@@index([archived])
|
|
@@index([brand])
|
|
}
|
|
|
|
model TagOnSupply {
|
|
supplyId String
|
|
tagId String
|
|
|
|
supply Supply @relation(fields: [supplyId], references: [id], onDelete: Cascade)
|
|
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade)
|
|
|
|
@@id([supplyId, tagId])
|
|
}
|
|
|
|
model UsageLog {
|
|
id String @id @default(cuid())
|
|
itemType String @db.VarChar(16)
|
|
itemId String
|
|
amount Float
|
|
unit String @db.VarChar(16)
|
|
notes String? @db.Text
|
|
createdAt DateTime @default(now())
|
|
userId String
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
filament Filament? @relation(fields: [filamentId], references: [id], onDelete: Cascade)
|
|
filamentId String?
|
|
resin Resin? @relation(fields: [resinId], references: [id], onDelete: Cascade)
|
|
resinId String?
|
|
paint Paint? @relation(fields: [paintId], references: [id], onDelete: Cascade)
|
|
paintId String?
|
|
supply Supply? @relation(fields: [supplyId], references: [id], onDelete: Cascade)
|
|
supplyId String?
|
|
|
|
@@index([userId])
|
|
@@index([itemType, itemId])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model UserSettings {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
lowStockThreshold Float @default(10)
|
|
currency String @default("USD") @db.VarChar(3)
|
|
theme String @default("dark") @db.VarChar(8)
|
|
units String @default("metric") @db.VarChar(8)
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
}
|
|
|
|
// ───────────────────────────────────────
|
|
// Telegram ingestion models
|
|
// ───────────────────────────────────────
|
|
|
|
enum AuthState {
|
|
PENDING
|
|
AWAITING_CODE
|
|
AWAITING_PASSWORD
|
|
AUTHENTICATED
|
|
EXPIRED
|
|
}
|
|
|
|
enum ChannelType {
|
|
SOURCE
|
|
DESTINATION
|
|
}
|
|
|
|
enum ChannelRole {
|
|
READER
|
|
WRITER
|
|
}
|
|
|
|
enum ArchiveType {
|
|
ZIP
|
|
RAR
|
|
SEVEN_Z
|
|
DOCUMENT
|
|
}
|
|
|
|
enum IngestionStatus {
|
|
RUNNING
|
|
COMPLETED
|
|
FAILED
|
|
CANCELLED
|
|
}
|
|
|
|
enum FetchStatus {
|
|
PENDING
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
model TelegramAccount {
|
|
id String @id @default(cuid())
|
|
phone String @unique
|
|
displayName String?
|
|
isActive Boolean @default(true)
|
|
authState AuthState @default(PENDING)
|
|
authCode String?
|
|
lastSeenAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
channelMaps AccountChannelMap[]
|
|
ingestionRuns IngestionRun[]
|
|
fetchRequests ChannelFetchRequest[]
|
|
|
|
@@index([isActive])
|
|
@@map("telegram_accounts")
|
|
}
|
|
|
|
model TelegramChannel {
|
|
id String @id @default(cuid())
|
|
telegramId BigInt @unique
|
|
title String
|
|
type ChannelType
|
|
isForum Boolean @default(false)
|
|
isActive Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
accountMaps AccountChannelMap[]
|
|
packages Package[]
|
|
|
|
@@index([type, isActive])
|
|
@@map("telegram_channels")
|
|
}
|
|
|
|
model AccountChannelMap {
|
|
id String @id @default(cuid())
|
|
accountId String
|
|
channelId String
|
|
role ChannelRole @default(READER)
|
|
lastProcessedMessageId BigInt?
|
|
createdAt DateTime @default(now())
|
|
|
|
account TelegramAccount @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
channel TelegramChannel @relation(fields: [channelId], references: [id], onDelete: Cascade)
|
|
topicProgress TopicProgress[]
|
|
|
|
@@unique([accountId, channelId])
|
|
@@index([accountId])
|
|
@@index([channelId])
|
|
@@map("account_channel_map")
|
|
}
|
|
|
|
model Package {
|
|
id String @id @default(cuid())
|
|
contentHash String @unique
|
|
fileName String
|
|
fileSize BigInt
|
|
archiveType ArchiveType
|
|
creator String?
|
|
sourceChannelId String
|
|
sourceMessageId BigInt
|
|
sourceTopicId BigInt?
|
|
destChannelId String?
|
|
destMessageId BigInt?
|
|
isMultipart Boolean @default(false)
|
|
partCount Int @default(1)
|
|
fileCount Int @default(0)
|
|
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())
|
|
createdAt DateTime @default(now())
|
|
|
|
sourceChannel TelegramChannel @relation(fields: [sourceChannelId], references: [id])
|
|
files PackageFile[]
|
|
ingestionRun IngestionRun? @relation(fields: [ingestionRunId], references: [id])
|
|
ingestionRunId String?
|
|
sendRequests BotSendRequest[]
|
|
|
|
@@index([sourceChannelId])
|
|
@@index([destChannelId])
|
|
@@index([fileName])
|
|
@@index([indexedAt])
|
|
@@index([archiveType])
|
|
@@index([creator])
|
|
@@map("packages")
|
|
}
|
|
|
|
model PackageFile {
|
|
id String @id @default(cuid())
|
|
packageId String
|
|
path String
|
|
fileName String
|
|
extension String?
|
|
compressedSize BigInt @default(0)
|
|
uncompressedSize BigInt @default(0)
|
|
crc32 String?
|
|
|
|
package Package @relation(fields: [packageId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([packageId])
|
|
@@index([extension])
|
|
@@index([fileName])
|
|
@@map("package_files")
|
|
}
|
|
|
|
model IngestionRun {
|
|
id String @id @default(cuid())
|
|
accountId String
|
|
status IngestionStatus @default(RUNNING)
|
|
startedAt DateTime @default(now())
|
|
finishedAt DateTime?
|
|
messagesScanned Int @default(0)
|
|
zipsFound Int @default(0)
|
|
zipsDuplicate Int @default(0)
|
|
zipsIngested Int @default(0)
|
|
errorMessage String?
|
|
|
|
// Live activity tracking — written by worker in real-time
|
|
currentActivity String? // Human-readable: "Downloading pack.zip (part 2/5)"
|
|
currentStep String? // Machine-readable step key
|
|
currentChannel String? // Channel title being processed
|
|
currentFile String? // File currently being processed
|
|
currentFileNum Int? // Which archive set (1-indexed)
|
|
totalFiles Int? // Total archive sets found
|
|
downloadedBytes BigInt? // Current download progress in bytes
|
|
totalBytes BigInt? // Total size of current download
|
|
downloadPercent Int? // 0-100
|
|
lastActivityAt DateTime? // When activity was last updated
|
|
|
|
account TelegramAccount @relation(fields: [accountId], references: [id])
|
|
packages Package[]
|
|
|
|
@@index([accountId])
|
|
@@index([status])
|
|
@@index([startedAt])
|
|
@@map("ingestion_runs")
|
|
}
|
|
|
|
model TopicProgress {
|
|
id String @id @default(cuid())
|
|
accountChannelMapId String
|
|
topicId BigInt
|
|
topicName String?
|
|
lastProcessedMessageId BigInt?
|
|
|
|
accountChannelMap AccountChannelMap @relation(fields: [accountChannelMapId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([accountChannelMapId, topicId])
|
|
@@index([accountChannelMapId])
|
|
@@map("topic_progress")
|
|
}
|
|
|
|
model GlobalSetting {
|
|
key String @id @db.VarChar(64)
|
|
value String @db.Text
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@map("global_settings")
|
|
}
|
|
|
|
model InviteCode {
|
|
id String @id @default(cuid())
|
|
code String @unique @db.VarChar(32)
|
|
maxUses Int @default(1)
|
|
uses Int @default(0)
|
|
expiresAt DateTime?
|
|
createdBy String
|
|
createdAt DateTime @default(now())
|
|
|
|
creator User @relation(fields: [createdBy], references: [id], onDelete: Cascade)
|
|
|
|
@@index([code])
|
|
@@map("invite_codes")
|
|
}
|
|
|
|
model ChannelFetchRequest {
|
|
id String @id @default(cuid())
|
|
accountId String
|
|
status FetchStatus @default(PENDING)
|
|
resultJson String? @db.Text
|
|
error String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
account TelegramAccount @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([accountId, status])
|
|
@@map("channel_fetch_requests")
|
|
}
|
|
|
|
// ───────────────────────────────────────
|
|
// Telegram Bot models
|
|
// ───────────────────────────────────────
|
|
|
|
enum BotSendStatus {
|
|
PENDING
|
|
SENDING
|
|
SENT
|
|
FAILED
|
|
}
|
|
|
|
/// Links a NextAuth user to a Telegram user ID.
|
|
/// Created when a user sends /link <code> to the bot.
|
|
model TelegramLink {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
telegramUserId BigInt @unique
|
|
telegramName String? @db.VarChar(128)
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
sendRequests BotSendRequest[]
|
|
|
|
@@map("telegram_links")
|
|
}
|
|
|
|
/// A queued request to send a package to a Telegram user via the bot.
|
|
model BotSendRequest {
|
|
id String @id @default(cuid())
|
|
packageId String
|
|
telegramLinkId String
|
|
requestedByUserId String
|
|
status BotSendStatus @default(PENDING)
|
|
error String?
|
|
createdAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
|
|
package Package @relation(fields: [packageId], references: [id])
|
|
telegramLink TelegramLink @relation(fields: [telegramLinkId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([status])
|
|
@@index([telegramLinkId])
|
|
@@index([createdAt])
|
|
@@map("bot_send_requests")
|
|
}
|
|
|
|
/// Subscriptions for new-package notifications via the bot.
|
|
model BotSubscription {
|
|
id String @id @default(cuid())
|
|
telegramUserId BigInt
|
|
pattern String @db.VarChar(256)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@unique([telegramUserId, pattern])
|
|
@@index([telegramUserId])
|
|
@@map("bot_subscriptions")
|
|
}
|