Files
songs2vid/prisma/schema.prisma
T
Atakan Doğan ÖzbanandCursor 7d22f722f6 Add dynamic footer badge marquee with admin management.
Store badges in Postgres, expose public/admin APIs, render an infinite footer slider, and ship /admin for paste-embed CRUD.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 19:58:24 +02:00

210 lines
6.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum Plan {
FREE
PREMIUM
}
enum Privacy {
PUBLIC
PRIVATE
UNLISTED
}
enum JobStatus {
PENDING
PROCESSING
COMPLETED
FAILED
PARTIAL
}
enum JobItemStatus {
PENDING
ENCODING
UPLOADING
COMPLETED
FAILED
}
enum QuotaExtensionRequestStatus {
PENDING
APPROVED
REJECTED
}
enum QuotaExtensionKind {
VIDEO_QUOTA
API_RATE_LIMIT
}
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
/// FREE = free tier; PREMIUM = Pro (€5/mo, formerly starter_5eur)
plan Plan @default(FREE)
/// Credits consumed in the current billing cycle (credits_used)
videosUsed Int @default(0)
/// Monthly allocation for the current cycle (10 free / 50 pro)
monthlyCredits Int @default(10)
quotaResetAt DateTime
cardLast4 String?
subscribedAt DateTime?
bonusQuota Int @default(0)
apiRateLimitBonus Int @default(0)
/// Purchased extras that never expire (maps to legacy "videoCredits" column)
extraCredits Int @default(0) @map("videoCredits")
/// Free-tier one-time +15 top-up already purchased
freeTopUpPurchased Boolean @default(false)
stripeCustomerId String?
stripeSubscriptionId String?
apiKeyHash String? @unique
apiKeyPrefix String?
youtubeConnection YouTubeConnection?
jobs Job[]
quotaExtensionRequests QuotaExtensionRequest[]
creditPurchases CreditPurchase[]
createdAt DateTime @default(now())
}
enum CreditPurchaseStatus {
PENDING
COMPLETED
FAILED
}
model CreditPurchase {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
credits Int
amountCents Int
status CreditPurchaseStatus @default(PENDING)
stripeSessionId String? @unique
stripePaymentIntentId String?
createdAt DateTime @default(now())
completedAt DateTime?
}
enum JobItemBilling {
QUOTA
CREDIT
}
model QuotaExtensionRequest {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
kind QuotaExtensionKind @default(VIDEO_QUOTA)
status QuotaExtensionRequestStatus @default(PENDING)
message String @default("")
requestedAt DateTime @default(now())
processedAt DateTime?
adminNote String?
}
model YouTubeConnection {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String
refreshToken String
expiresAt DateTime
channelId String
channelTitle String
}
model Job {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
status JobStatus @default(PENDING)
imagePath String
items JobItem[]
createdAt DateTime @default(now())
completedAt DateTime?
}
model JobItem {
id String @id @default(cuid())
jobId String
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
audioPath String
audioFilename String
title String
description String @default("")
tags String @default("")
privacy Privacy @default(PUBLIC)
categoryId String @default("10")
resolution String @default("1280x720")
notifySubscribers Boolean @default(true)
madeForKids Boolean @default(false)
embeddable Boolean @default(true)
creativeCommons Boolean @default(false)
includeWatermark Boolean @default(true)
/// Optional per-item cover (Pro). Falls back to Job.imagePath when null.
itemImagePath String?
/// none | default | text | logo custom modes require Pro
watermarkMode String @default("default")
watermarkText String?
watermarkLogoPath String?
/// curated key | custom | system
watermarkFontKey String?
/// uploaded .ttf/.otf when fontKey=custom
watermarkFontPath String?
/// top-left | top-right | bottom-left | bottom-right | center
watermarkPosition String @default("bottom-right")
watermarkOffsetX Int @default(20)
watermarkOffsetY Int @default(20)
/// Optional artist line for art-track layouts (Pro, on-video only)
artist String?
/// Song title burned into art-track layouts (Pro, on-video only)
songTitle String?
/// COVER_LEFT_TEXT_RIGHT | COVER_TOP_TEXT_BOTTOM | COVER_RIGHT_TEXT_LEFT | CENTERED_COMPACT | null=classic
layoutTemplate String?
/// 0100 Gaussian / boxblur intensity (Pro art-track)
blurAmount Int @default(55)
/// 0100 visibility of blurred fill vs black
blurOpacity Int @default(100)
/// Padding around cover + text in art-track layouts
textPadding Int @default(48)
/// Extra gap between title and artist lines (px)
titleArtistGap Int @default(10)
/// Fine-tune text block within template (-120..120)
textOffsetX Int @default(0)
textOffsetY Int @default(0)
playlistId String?
billingSource JobItemBilling @default(QUOTA)
status JobItemStatus @default(PENDING)
outputPath String?
youtubeVideoId String?
error String?
}
/// Site footer badge / launch-directory embeds (managed via /admin)
model FooterBadge {
id String @id @default(cuid())
name String
/// Raw paste from Product Hunt / LaunchBuff / etc. (preferred source)
embedHtml String? @db.Text
/// Parsed or manually set image URL (used when embedHtml is empty)
imageUrl String?
/// Parsed or manually set destination URL
linkUrl String?
isActive Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isActive, sortOrder])
}