Align preview typography with FFmpeg output, add video/song title split, and ship OSS updates.

Separate YouTube video titles from on-video song/artist fields with Pro gating, serve curated fonts and watermark assets for 1:1 preview parity, and include billing/API/docs/deploy stack for self-hosted release.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Atakan Doğan Özban
2026-07-27 16:26:19 +02:00
co-authored by Cursor
parent 682020ff5a
commit c8015937f9
206 changed files with 37078 additions and 638 deletions
+304
View File
@@ -0,0 +1,304 @@
import { Plan } from "@prisma/client";
import {
EXTRA_CREDITS_MAX,
FREE_TOP_UP_CREDITS,
MAX_CREDIT_CAP,
monthlyCreditsForPlan,
} from "./credits";
import { prisma } from "./db";
import { getNextMonthlyQuotaReset } from "./plans";
export class CreditInsufficientError extends Error {
readonly status = 402;
constructor(message = "Payment Required: no credits remaining.") {
super(message);
this.name = "CreditInsufficientError";
}
}
export type DeductResult = {
fromMonthly: number;
fromExtra: number;
};
export type RenewalBalances = {
monthlyCredits: number;
videosUsed: number;
extraCredits: number;
bonusQuota: number;
trimmed: number;
totalAfter: number;
};
/**
* Rollover + hard cap on renewal.
* prospective = unused (monthly remaining + extras) + newMonthly
* If prospective > MAX_CREDIT_CAP (30), trim excess (no refund).
*
* Storage: prefer filling the new monthly allocation first, leftover as extras.
*/
export function computeRenewalBalances(input: {
monthlyCredits: number;
videosUsed: number;
bonusQuota?: number;
extraCredits: number;
newMonthlyCredits: number;
maxCap?: number;
}): RenewalBalances {
const cap = input.maxCap ?? MAX_CREDIT_CAP;
const bonus = input.bonusQuota ?? 0;
const unusedMonthly = Math.max(0, input.monthlyCredits + bonus - input.videosUsed);
const currentUnused = unusedMonthly + Math.max(0, input.extraCredits);
const prospective = currentUnused + input.newMonthlyCredits;
const totalAfter = Math.min(prospective, cap);
const trimmed = Math.max(0, prospective - totalAfter);
const monthlyCredits = Math.min(input.newMonthlyCredits, totalAfter);
const extraCredits = Math.max(0, totalAfter - monthlyCredits);
return {
monthlyCredits,
videosUsed: 0,
extraCredits,
bonusQuota: 0,
trimmed,
totalAfter,
};
}
/**
* Apply monthly credit renewal with MAX_CREDIT_CAP rollover trim.
*/
export async function applyMonthlyCreditRenewal(
userId: string,
opts: {
plan?: Plan;
newMonthlyCredits?: number;
quotaResetAt?: Date;
} = {},
) {
return prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({ where: { id: userId } });
const plan = opts.plan ?? user.plan;
const newMonthly = opts.newMonthlyCredits ?? monthlyCreditsForPlan(plan);
const balances = computeRenewalBalances({
monthlyCredits: user.monthlyCredits,
videosUsed: user.videosUsed,
bonusQuota: user.bonusQuota,
extraCredits: user.extraCredits,
newMonthlyCredits: newMonthly,
});
return tx.user.update({
where: { id: userId },
data: {
plan,
monthlyCredits: balances.monthlyCredits,
videosUsed: balances.videosUsed,
extraCredits: balances.extraCredits,
bonusQuota: balances.bonusQuota,
quotaResetAt: opts.quotaResetAt ?? getNextMonthlyQuotaReset(),
},
});
});
}
/**
* Deduct video creation credits for `count` items.
* Order: monthly allocation first, then extraCredits.
*/
export async function deductUserCredit(
userId: string,
count = 1,
): Promise<DeductResult> {
if (count <= 0) return { fromMonthly: 0, fromExtra: 0 };
return prisma.$transaction(async (tx) => {
const rows = await tx.$queryRaw<
Array<{
id: string;
plan: Plan;
videosUsed: number;
monthlyCredits: number;
bonusQuota: number;
videoCredits: number;
}>
>`SELECT id, plan, "videosUsed", "monthlyCredits", "bonusQuota", "videoCredits"
FROM "User" WHERE id = ${userId} FOR UPDATE`;
const user = rows[0];
if (!user) throw new Error("User not found");
const monthlyLimit = user.monthlyCredits + user.bonusQuota;
const monthlyRemaining = Math.max(0, monthlyLimit - user.videosUsed);
const extra = user.videoCredits;
const total = monthlyRemaining + extra;
if (total < count) {
throw new CreditInsufficientError(
`Not enough credits. Available: ${total} (monthly remaining ${monthlyRemaining} + extras ${extra}).`,
);
}
const fromMonthly = Math.min(count, monthlyRemaining);
const fromExtra = count - fromMonthly;
await tx.user.update({
where: { id: userId },
data: {
...(fromMonthly > 0 ? { videosUsed: { increment: fromMonthly } } : {}),
...(fromExtra > 0 ? { extraCredits: { decrement: fromExtra } } : {}),
},
});
return { fromMonthly, fromExtra };
});
}
/** Refund reserved credits (job create failure / worker failure). */
export async function refundUserCredit(
userId: string,
fromMonthly: number,
fromExtra: number,
) {
if (fromMonthly > 0) {
await prisma.$executeRaw`
UPDATE "User"
SET "videosUsed" = GREATEST(0, "videosUsed" - ${fromMonthly})
WHERE id = ${userId}
`;
}
if (fromExtra > 0) {
await prisma.$executeRaw`
UPDATE "User"
SET "videoCredits" = LEAST(${EXTRA_CREDITS_MAX}, "videoCredits" + ${fromExtra})
WHERE id = ${userId}
`;
}
}
/**
* Admin / support: reset or adjust a user's billing cycle and extras.
*/
export async function adminResetUserCredits(
userId: string,
options: {
plan?: Plan;
monthlyCredits?: number;
creditsUsed?: number;
extraCredits?: number;
clearFreeTopUp?: boolean;
} = {},
) {
const user = await prisma.user.findUniqueOrThrow({ where: { id: userId } });
const plan = options.plan ?? user.plan;
const monthlyCredits =
options.monthlyCredits ?? monthlyCreditsForPlan(plan);
const extras =
options.extraCredits !== undefined
? Math.max(0, Math.min(EXTRA_CREDITS_MAX, options.extraCredits))
: user.extraCredits;
const used = options.creditsUsed ?? 0;
const remainingMonthly = Math.max(0, monthlyCredits - used);
const total = remainingMonthly + extras;
const cappedExtras =
total > MAX_CREDIT_CAP
? Math.max(0, MAX_CREDIT_CAP - remainingMonthly)
: extras;
return prisma.user.update({
where: { id: userId },
data: {
plan,
monthlyCredits,
videosUsed: used,
extraCredits: cappedExtras,
...(options.clearFreeTopUp ? { freeTopUpPurchased: false } : {}),
quotaResetAt: getNextMonthlyQuotaReset(),
...(plan === "FREE"
? {
stripeSubscriptionId: null,
subscribedAt: null,
cardLast4: null,
bonusQuota: 0,
}
: {}),
},
});
}
export async function activateProPlan(
userId: string,
opts: {
stripeCustomerId?: string | null;
stripeSubscriptionId?: string | null;
cardLast4?: string | null;
} = {},
) {
await applyMonthlyCreditRenewal(userId, {
plan: "PREMIUM",
newMonthlyCredits: monthlyCreditsForPlan("PREMIUM"),
});
return prisma.user.update({
where: { id: userId },
data: {
plan: "PREMIUM",
subscribedAt: new Date(),
...(opts.stripeCustomerId !== undefined
? { stripeCustomerId: opts.stripeCustomerId }
: {}),
...(opts.stripeSubscriptionId !== undefined
? { stripeSubscriptionId: opts.stripeSubscriptionId }
: {}),
...(opts.cardLast4 !== undefined ? { cardLast4: opts.cardLast4 } : {}),
},
});
}
export async function downgradeToFreePlan(userId: string) {
await applyMonthlyCreditRenewal(userId, {
plan: "FREE",
newMonthlyCredits: monthlyCreditsForPlan("FREE"),
});
return prisma.user.update({
where: { id: userId },
data: {
plan: "FREE",
stripeSubscriptionId: null,
subscribedAt: null,
cardLast4: null,
apiKeyHash: null,
apiKeyPrefix: null,
apiRateLimitBonus: 0,
},
});
}
export async function grantExtraCredits(userId: string, credits: number) {
if (credits <= 0) return;
await prisma.$transaction(async (tx) => {
const user = await tx.user.findUniqueOrThrow({ where: { id: userId } });
const monthlyRemaining = Math.max(
0,
user.monthlyCredits + user.bonusQuota - user.videosUsed,
);
const room = Math.max(0, MAX_CREDIT_CAP - monthlyRemaining - user.extraCredits);
const grant = Math.min(credits, room);
if (grant <= 0) return;
await tx.user.update({
where: { id: userId },
data: { extraCredits: { increment: grant } },
});
});
}
export async function markFreeTopUpPurchased(userId: string) {
await prisma.user.update({
where: { id: userId },
data: { freeTopUpPurchased: true },
});
await grantExtraCredits(userId, FREE_TOP_UP_CREDITS);
}