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
@@ -0,0 +1,217 @@
import { NextRequest, NextResponse } from "next/server";
import { downgradeToFreePlan } from "@/lib/billing";
import { prisma } from "@/lib/db";
import { getSessionUser } from "@/lib/session";
import { getStripe, isBillingDevMock, isStripeConfigured } from "@/lib/stripe";
type CancelWhen = "immediate" | "period_end";
type CancelAction = "cancel" | "resume";
function periodEndIso(sub: {
cancel_at?: number | null;
current_period_end?: number;
items?: { data?: Array<{ current_period_end?: number }> };
}): string | null {
const fromItem = sub.items?.data?.[0]?.current_period_end;
const ts = sub.cancel_at ?? fromItem ?? sub.current_period_end ?? null;
return ts ? new Date(ts * 1000).toISOString() : null;
}
/**
* Cancel Pro subscription via Stripe.
* Body: { when: "immediate" | "period_end" } or { action: "resume" }
*/
export async function POST(req: NextRequest) {
const user = await getSessionUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (user.plan !== "PREMIUM") {
return NextResponse.json({ error: "No active subscription to cancel" }, { status: 400 });
}
let action: CancelAction = "cancel";
let when: CancelWhen = "immediate";
try {
const body = await req.json();
if (body?.action === "resume") action = "resume";
if (body?.when === "period_end" || body?.when === "immediate") when = body.when;
} catch {
// empty body → default immediate cancel (legacy)
}
const dbUser = await prisma.user.findUniqueOrThrow({
where: { id: user.id },
select: { stripeSubscriptionId: true },
});
const subId = dbUser.stripeSubscriptionId;
const isMockSub = !subId || subId.startsWith("dev_sub_");
const useStripe =
Boolean(subId) &&
!isMockSub &&
isStripeConfigured() &&
!isBillingDevMock();
// Resume (undo cancel-at-period-end)
if (action === "resume") {
if (useStripe && subId) {
try {
const stripe = getStripe();
const sub = await stripe.subscriptions.update(subId, {
cancel_at_period_end: false,
});
return NextResponse.json({
ok: true,
action: "resume",
cancelAtPeriodEnd: false,
currentPeriodEnd: periodEndIso(sub as { current_period_end?: number }),
});
} catch (err) {
console.error("[stripe] resume subscription failed", err);
return NextResponse.json(
{ error: err instanceof Error ? err.message : "Failed to keep subscription" },
{ status: 502 },
);
}
}
return NextResponse.json({
ok: true,
action: "resume",
cancelAtPeriodEnd: false,
mocked: true,
});
}
// Cancel
if (when === "period_end") {
if (useStripe && subId) {
try {
const stripe = getStripe();
const sub = await stripe.subscriptions.update(subId, {
cancel_at_period_end: true,
});
const endsAt = periodEndIso(
sub as {
cancel_at?: number | null;
current_period_end?: number;
items?: { data?: Array<{ current_period_end?: number }> };
},
);
return NextResponse.json({
ok: true,
when: "period_end",
cancelAtPeriodEnd: true,
endsAt,
// Keep Pro until Stripe sends customer.subscription.deleted
});
} catch (err) {
console.error("[stripe] cancel at period end failed", err);
return NextResponse.json(
{ error: err instanceof Error ? err.message : "Failed to schedule cancellation" },
{ status: 502 },
);
}
}
// Mock / no Stripe id: schedule locally by leaving Pro and reporting next month
const endsAt = new Date();
endsAt.setMonth(endsAt.getMonth() + 1);
return NextResponse.json({
ok: true,
when: "period_end",
cancelAtPeriodEnd: true,
endsAt: endsAt.toISOString(),
mocked: true,
});
}
// immediate
if (useStripe && subId) {
try {
const stripe = getStripe();
await stripe.subscriptions.cancel(subId);
} catch (err) {
console.error("[stripe] cancel subscription failed", err);
return NextResponse.json(
{
error:
err instanceof Error
? err.message
: "Stripe could not cancel the subscription. Your plan was not changed.",
},
{ status: 502 },
);
}
}
await downgradeToFreePlan(user.id);
return NextResponse.json({
ok: true,
when: "immediate",
cancelAtPeriodEnd: false,
plan: "FREE",
});
}
/** Current Stripe subscription cancel status for settings UI. */
export async function GET() {
const user = await getSessionUser();
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (user.plan !== "PREMIUM") {
return NextResponse.json({
plan: user.plan,
cancelAtPeriodEnd: false,
endsAt: null,
});
}
const dbUser = await prisma.user.findUniqueOrThrow({
where: { id: user.id },
select: { stripeSubscriptionId: true },
});
const subId = dbUser.stripeSubscriptionId;
if (
!subId ||
subId.startsWith("dev_sub_") ||
!isStripeConfigured() ||
isBillingDevMock()
) {
return NextResponse.json({
plan: "PREMIUM",
cancelAtPeriodEnd: false,
endsAt: null,
mocked: true,
});
}
try {
const stripe = getStripe();
const sub = await stripe.subscriptions.retrieve(subId);
return NextResponse.json({
plan: "PREMIUM",
status: sub.status,
cancelAtPeriodEnd: Boolean(sub.cancel_at_period_end),
endsAt: periodEndIso(
sub as {
cancel_at?: number | null;
current_period_end?: number;
items?: { data?: Array<{ current_period_end?: number }> };
},
),
});
} catch (err) {
console.error("[stripe] retrieve subscription failed", err);
return NextResponse.json({
plan: "PREMIUM",
cancelAtPeriodEnd: false,
endsAt: null,
error: "Could not load subscription status from Stripe",
});
}
}