mirror of
https://git.atakanozban.com/Songs2VID/songs2vid.git
synced 2026-11-14 21:29:49 +01:00
220 lines
7.1 KiB
Plaintext
220 lines
7.1 KiB
Plaintext
|
|
generator client {
|
|||
|
|
provider = "prisma-client-js"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
datasource db {
|
|||
|
|
provider = "postgresql"
|
|||
|
|
url = env("DATABASE_URL")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum Plan {
|
|||
|
|
FREE
|
|||
|
|
PREMIUM
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Public-facing subscription tier (kept in sync with `plan`: FREE→free, PREMIUM→pro)
|
|||
|
|
enum SubscriptionStatus {
|
|||
|
|
free
|
|||
|
|
pro
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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)
|
|||
|
|
/// Mirrors plan for product/UI (`free` | `pro`). Prefer `plan` for entitlements.
|
|||
|
|
subscriptionStatus SubscriptionStatus @default(free)
|
|||
|
|
/// Lifetime count of successfully completed video renders (not monthly quota).
|
|||
|
|
createdVideoCount Int @default(0)
|
|||
|
|
/// 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?
|
|||
|
|
/// 0–100 Gaussian / boxblur intensity (Pro art-track)
|
|||
|
|
blurAmount Int @default(55)
|
|||
|
|
/// 0–100 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])
|
|||
|
|
}
|