Initial open-source self-hosted Songs2YT edition

This commit is contained in:
Songs2YT
2026-07-17 02:23:59 +02:00
commit 2aa8a84781
134 changed files with 17758 additions and 0 deletions
@@ -0,0 +1,90 @@
-- CreateEnum
CREATE TYPE "Plan" AS ENUM ('FREE', 'PREMIUM');
-- CreateEnum
CREATE TYPE "Privacy" AS ENUM ('PUBLIC', 'PRIVATE', 'UNLISTED');
-- CreateEnum
CREATE TYPE "JobStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', 'PARTIAL');
-- CreateEnum
CREATE TYPE "JobItemStatus" AS ENUM ('PENDING', 'ENCODING', 'UPLOADING', 'COMPLETED', 'FAILED');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
"image" TEXT,
"plan" "Plan" NOT NULL DEFAULT 'FREE',
"videosUsed" INTEGER NOT NULL DEFAULT 0,
"quotaResetAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "YouTubeConnection" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"accessToken" TEXT NOT NULL,
"refreshToken" TEXT NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"channelId" TEXT NOT NULL,
"channelTitle" TEXT NOT NULL,
CONSTRAINT "YouTubeConnection_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Job" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"status" "JobStatus" NOT NULL DEFAULT 'PENDING',
"imagePath" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
CONSTRAINT "Job_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "JobItem" (
"id" TEXT NOT NULL,
"jobId" TEXT NOT NULL,
"audioPath" TEXT NOT NULL,
"audioFilename" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL DEFAULT '',
"tags" TEXT NOT NULL DEFAULT '',
"privacy" "Privacy" NOT NULL DEFAULT 'PUBLIC',
"categoryId" TEXT NOT NULL DEFAULT '10',
"resolution" TEXT NOT NULL DEFAULT '1280x720',
"notifySubscribers" BOOLEAN NOT NULL DEFAULT true,
"madeForKids" BOOLEAN NOT NULL DEFAULT false,
"embeddable" BOOLEAN NOT NULL DEFAULT true,
"creativeCommons" BOOLEAN NOT NULL DEFAULT false,
"includeWatermark" BOOLEAN NOT NULL DEFAULT true,
"status" "JobItemStatus" NOT NULL DEFAULT 'PENDING',
"outputPath" TEXT,
"youtubeVideoId" TEXT,
"error" TEXT,
CONSTRAINT "JobItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "YouTubeConnection_userId_key" ON "YouTubeConnection"("userId");
-- AddForeignKey
ALTER TABLE "YouTubeConnection" ADD CONSTRAINT "YouTubeConnection_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Job" ADD CONSTRAINT "Job_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "JobItem" ADD CONSTRAINT "JobItem_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "cardLast4" TEXT,
ADD COLUMN "subscribedAt" TIMESTAMP(3);
@@ -0,0 +1,21 @@
-- CreateEnum
CREATE TYPE "QuotaExtensionRequestStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
-- AlterTable
ALTER TABLE "User" ADD COLUMN "bonusQuota" INTEGER NOT NULL DEFAULT 0;
-- CreateTable
CREATE TABLE "QuotaExtensionRequest" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"status" "QuotaExtensionRequestStatus" NOT NULL DEFAULT 'PENDING',
"message" TEXT NOT NULL DEFAULT '',
"requestedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"processedAt" TIMESTAMP(3),
"adminNote" TEXT,
CONSTRAINT "QuotaExtensionRequest_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "QuotaExtensionRequest" ADD CONSTRAINT "QuotaExtensionRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "apiKeyHash" TEXT,
ADD COLUMN "apiKeyPrefix" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "User_apiKeyHash_key" ON "User"("apiKeyHash");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "JobItem" ADD COLUMN "playlistId" TEXT;
@@ -0,0 +1,8 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "apiRateLimitBonus" INTEGER NOT NULL DEFAULT 0;
-- CreateEnum
CREATE TYPE "QuotaExtensionKind" AS ENUM ('VIDEO_QUOTA', 'API_RATE_LIMIT');
-- AlterTable
ALTER TABLE "QuotaExtensionRequest" ADD COLUMN "kind" "QuotaExtensionKind" NOT NULL DEFAULT 'VIDEO_QUOTA';
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+152
View File
@@ -0,0 +1,152 @@
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?
plan Plan @default(FREE)
videosUsed Int @default(0)
quotaResetAt DateTime
cardLast4 String?
subscribedAt DateTime?
bonusQuota Int @default(0)
apiRateLimitBonus Int @default(0)
/** Prepaid pay-as-you-go video credits (1 credit = 1 video). Cap: 1000. */
videoCredits Int @default(0)
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)
playlistId String?
billingSource JobItemBilling @default(QUOTA)
status JobItemStatus @default(PENDING)
outputPath String?
youtubeVideoId String?
error String?
}