Initial OSS scaffold from Songs2VID (pre-strip)

This commit is contained in:
Songs2VID Support
2026-08-03 06:15:05 +02:00
commit 506d56fa92
195 changed files with 25023 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
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?
/// 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])
}