Files
songs2vid/app/api/dev/grant-credits/route.ts
T
Atakan Doğan Özban 9404efd86c 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.
2026-07-27 16:26:19 +02:00

96 lines
2.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import {
activateProPlan,
adminResetUserCredits,
grantExtraCredits,
markFreeTopUpPurchased,
} from "@/lib/billing";
import { FREE_TOP_UP_CREDITS } from "@/lib/credits";
import { isBillingDevMock } from "@/lib/stripe";
import { getSessionUser } from "@/lib/session";
/**
* Local-only billing helpers (no Stripe CLI required).
* Enabled when NODE_ENV=development (unless BILLING_DEV_MOCK=false) or BILLING_DEV_MOCK=true.
*/
function assertDev() {
if (!isBillingDevMock()) {
return NextResponse.json({ error: "Not available outside development mock mode" }, { status: 404 });
}
return null;
}
export async function POST(req: NextRequest) {
const blocked = assertDev();
if (blocked) return blocked;
const user = await getSessionUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
let body: {
action?: string;
credits?: number;
plan?: "FREE" | "PREMIUM";
};
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
}
const action = body.action ?? "grant-credits";
switch (action) {
case "grant-credits": {
const credits = Math.floor(Number(body.credits ?? FREE_TOP_UP_CREDITS));
if (!Number.isFinite(credits) || credits <= 0) {
return NextResponse.json({ error: "Invalid credits" }, { status: 400 });
}
await grantExtraCredits(user.id, credits);
return NextResponse.json({ ok: true, granted: credits });
}
case "free-top-up": {
await markFreeTopUpPurchased(user.id);
return NextResponse.json({ ok: true, granted: FREE_TOP_UP_CREDITS });
}
case "set-pro": {
await activateProPlan(user.id, {
stripeSubscriptionId: `dev_sub_${Date.now()}`,
cardLast4: "4242",
});
return NextResponse.json({ ok: true, plan: "PREMIUM" });
}
case "set-free": {
await adminResetUserCredits(user.id, { plan: "FREE", creditsUsed: 0 });
return NextResponse.json({ ok: true, plan: "FREE" });
}
case "reset-cycle": {
await adminResetUserCredits(user.id, {
plan: body.plan,
creditsUsed: 0,
});
return NextResponse.json({ ok: true, reset: true });
}
default:
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
}
}
/** Convenience GET for quick browser testing: /api/dev/grant-credits?action=set-pro */
export async function GET(req: NextRequest) {
const blocked = assertDev();
if (blocked) return blocked;
const action = req.nextUrl.searchParams.get("action") ?? "grant-credits";
const credits = Number(req.nextUrl.searchParams.get("credits") ?? FREE_TOP_UP_CREDITS);
const fake = new NextRequest(req.url, {
method: "POST",
headers: { "content-type": "application/json", cookie: req.headers.get("cookie") ?? "" },
body: JSON.stringify({ action, credits }),
});
return POST(fake);
}