120 lines
3.4 KiB
Plaintext
120 lines
3.4 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum MediaType {
|
|
IMAGE
|
|
VIDEO
|
|
AUDIO
|
|
OTHER
|
|
}
|
|
|
|
enum PlacementMode {
|
|
RANKING // Position determined by likes ranking
|
|
FIXED_POSITION // Position manually set by admin
|
|
FIXED_SIZE_RANKING // Position from ranking, but size is fixed
|
|
FIXED_POSITION_ADJUSTABLE // Position and size set by admin
|
|
}
|
|
|
|
model MediaItem {
|
|
id Int @id @default(autoincrement())
|
|
relativePath String @unique // Path relative to MEDIA_ROOT, e.g. "Fotobox/001.jpg"
|
|
filename String // Just the filename, e.g. "001.jpg"
|
|
directory String // Parent directory, e.g. "Fotobox"
|
|
mediaType MediaType @default(IMAGE)
|
|
mimeType String @default("application/octet-stream")
|
|
fileSize BigInt @default(0)
|
|
width Int? // Pixels, for images and videos
|
|
height Int? // Pixels, for images and videos
|
|
duration Float? // Seconds, for video and audio
|
|
|
|
// Publication
|
|
published Boolean @default(false)
|
|
wasOncePublished Boolean @default(false)
|
|
|
|
// Layout / Placement
|
|
featured Boolean @default(false) // Prominent/title image
|
|
placementMode PlacementMode @default(RANKING)
|
|
gridColumn Int? // For fixed position (grid column start)
|
|
gridRow Int? // For fixed position (grid row start)
|
|
gridWidth Int @default(1) // Width in grid units
|
|
gridHeight Int @default(1) // Height in grid units
|
|
sortOrder Int @default(0) // Manual sort order / ranking tiebreak
|
|
|
|
// Processing status
|
|
thumbnailReady Boolean @default(false)
|
|
previewReady Boolean @default(false)
|
|
|
|
// File tracking
|
|
fileHash String? // For detecting file changes
|
|
lastModified DateTime? // File's mtime from filesystem
|
|
lastScanned DateTime @default(now())
|
|
|
|
// Timestamps
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
// Relations
|
|
likes Like[]
|
|
|
|
@@index([published])
|
|
@@index([directory])
|
|
@@index([mediaType])
|
|
@@index([published, sortOrder])
|
|
@@index([published, placementMode])
|
|
}
|
|
|
|
model Like {
|
|
id Int @id @default(autoincrement())
|
|
mediaItemId Int
|
|
ipHash String // SHA-256 hash of IP address
|
|
fingerprint String? // Optional browser fingerprint hash
|
|
createdAt DateTime @default(now())
|
|
|
|
mediaItem MediaItem @relation(fields: [mediaItemId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([mediaItemId, ipHash]) // One like per IP per media item
|
|
@@index([ipHash])
|
|
@@index([mediaItemId])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model AppSettings {
|
|
id Int @id @default(1)
|
|
votingOpen Boolean @default(false)
|
|
zipVersion Int @default(0)
|
|
zipReady Boolean @default(false)
|
|
zipBuildAt DateTime?
|
|
zipFilePath String? // Path to current valid ZIP
|
|
lastScanAt DateTime?
|
|
directoryOrder String[] @default([])
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model AdminSession {
|
|
id String @id @default(uuid())
|
|
ipAddress String
|
|
userAgent String @default("")
|
|
createdAt DateTime @default(now())
|
|
expiresAt DateTime
|
|
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model RateLimitEntry {
|
|
id Int @id @default(autoincrement())
|
|
ipHash String
|
|
action String // e.g. "login", "like"
|
|
windowStart DateTime
|
|
count Int @default(1)
|
|
|
|
@@unique([ipHash, action, windowStart])
|
|
@@index([ipHash, action, windowStart])
|
|
@@index([windowStart]) // For cleanup of old entries
|
|
}
|