Unlock features without credits/Stripe, point docs to docs.songs2vid.com, and drop leftover billing admin surfaces. Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
3.8 KiB
TypeScript
134 lines
3.8 KiB
TypeScript
import { Plan } from "@prisma/client";
|
|
import { prisma } from "./db";
|
|
import { getNextMonthlyQuotaReset, getPlanLimits } from "./plans";
|
|
|
|
/**
|
|
* OSS / self-hosted quota: never blocks on credits or plan limits.
|
|
* Batch size still enforced via SELFHOSTED_LIMITS.maxBatchSize.
|
|
* videosUsed is incremented for display only.
|
|
*/
|
|
|
|
export function formatQuotaResetCountdown(_resetsAt: Date | string): string {
|
|
return "never";
|
|
}
|
|
|
|
export function formatQuotaResetDisplay(_plan: Plan, _resetsAt: Date | string): string {
|
|
return "never (self-hosted)";
|
|
}
|
|
|
|
export async function ensureQuotaReset(userId: string) {
|
|
return prisma.user.findUniqueOrThrow({ where: { id: userId } });
|
|
}
|
|
|
|
export async function getQuotaInfo(userId: string) {
|
|
const user = await ensureQuotaReset(userId);
|
|
const limits = getPlanLimits(user.plan);
|
|
return {
|
|
used: user.videosUsed,
|
|
limit: limits.monthlyQuota,
|
|
baseLimit: limits.monthlyQuota,
|
|
bonusQuota: 0,
|
|
remaining: limits.monthlyQuota,
|
|
videoCredits: 0,
|
|
extraCredits: 0,
|
|
creditBalanceMax: 0,
|
|
totalAvailable: limits.monthlyQuota,
|
|
resetsAt: user.quotaResetAt.toISOString(),
|
|
resetsIn: formatQuotaResetDisplay(user.plan, user.quotaResetAt),
|
|
plan: user.plan,
|
|
maxBatchSize: limits.maxBatchSize,
|
|
watermarkOptional: limits.watermarkOptional,
|
|
customWatermark: limits.customWatermark,
|
|
perItemImages: limits.perItemImages,
|
|
artTrackLayouts: limits.artTrackLayouts,
|
|
maxResolutionHeight: limits.maxResolutionHeight,
|
|
selfHosted: true,
|
|
};
|
|
}
|
|
|
|
export type ReservationSplit = {
|
|
fromQuota: number;
|
|
fromCredits: number;
|
|
};
|
|
|
|
/** Always succeed (except empty). Credits are never used in OSS. */
|
|
export function planReservation(
|
|
_plan: Plan,
|
|
_remainingQuota: number,
|
|
_videoCredits: number,
|
|
count: number,
|
|
): ReservationSplit | null {
|
|
if (count <= 0) return { fromQuota: 0, fromCredits: 0 };
|
|
return { fromQuota: count, fromCredits: 0 };
|
|
}
|
|
|
|
export async function checkQuota(userId: string, requestedCount: number) {
|
|
const info = await getQuotaInfo(userId);
|
|
if (requestedCount > info.maxBatchSize) {
|
|
return {
|
|
ok: false as const,
|
|
error: `Batch limit exceeded. Max ${info.maxBatchSize} files per batch.`,
|
|
used: info.used,
|
|
limit: info.limit,
|
|
remaining: info.remaining,
|
|
videoCredits: 0,
|
|
resetsAt: info.resetsAt,
|
|
};
|
|
}
|
|
return {
|
|
ok: true as const,
|
|
...info,
|
|
reservation: { fromQuota: requestedCount, fromCredits: 0 },
|
|
};
|
|
}
|
|
|
|
/** Track usage for display; never deduct credits or block. */
|
|
export async function reserveQuota(userId: string, count: number): Promise<ReservationSplit> {
|
|
if (count <= 0) return { fromQuota: 0, fromCredits: 0 };
|
|
|
|
const limits = getPlanLimits();
|
|
if (count > limits.maxBatchSize) {
|
|
throw new Error(`Batch limit exceeded. Max ${limits.maxBatchSize} files per batch.`);
|
|
}
|
|
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: { videosUsed: { increment: count } },
|
|
});
|
|
return { fromQuota: count, fromCredits: 0 };
|
|
}
|
|
|
|
export async function releaseQuota(userId: string, count: number) {
|
|
if (count <= 0) return;
|
|
await prisma.$executeRaw`
|
|
UPDATE "User"
|
|
SET "videosUsed" = GREATEST(0, "videosUsed" - ${count})
|
|
WHERE id = ${userId}
|
|
`;
|
|
}
|
|
|
|
/** No-op in OSS (no prepaid credits). */
|
|
export async function releaseCredits(_userId: string, _count: number) {
|
|
return;
|
|
}
|
|
|
|
export async function releaseReservation(
|
|
userId: string,
|
|
_billingSource: "QUOTA" | "CREDIT",
|
|
count = 1,
|
|
) {
|
|
await releaseQuota(userId, count);
|
|
}
|
|
|
|
export async function releaseReservationSplit(userId: string, split: ReservationSplit) {
|
|
if (split.fromQuota > 0) await releaseQuota(userId, split.fromQuota);
|
|
}
|
|
|
|
export async function incrementQuota(userId: string, count: number) {
|
|
await reserveQuota(userId, count);
|
|
}
|
|
|
|
export function getInitialQuotaResetAt(): Date {
|
|
return getNextMonthlyQuotaReset();
|
|
}
|