chahinebrini eb871073f2 feat(backend): __DEV__ /api/dev/set-plan — user sets own plan (non-prod only)
POST /api/dev/set-plan { plan: 'free'|'pro'|'legend' } — requireUser, sets the
caller's own profile.plan via Prisma. Refuses on production URL (same guard as
the cooldown testMode: appUrl includes rebreak.org && !includes staging). Lets
the __DEV__ tier-toggle work without admin rights. Does NOT weaken updateProfile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:44:26 +02:00

48 lines
1.4 KiB
TypeScript

import { usePrisma } from "../../utils/prisma";
const VALID_PLANS = ["free", "pro", "legend"] as const;
type AppPlan = (typeof VALID_PLANS)[number];
/**
* POST /api/dev/set-plan
*
* DEV/STAGING-ONLY: Setzt den eigenen Plan ohne Admin-Rechte.
* Blocked in Production (appUrl enthält "rebreak.org" aber NICHT "staging").
*
* Body: { plan: "free" | "pro" | "legend" }
* Response: { success: true, plan: AppPlan }
*/
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
// Prod-Guard: analog cooldown/request.post.ts
const config = useRuntimeConfig(event);
const appUrl = (config.public?.appUrl as string) ?? "";
const isProductionUrl =
appUrl.includes("rebreak.org") && !appUrl.includes("staging");
if (isProductionUrl) {
throw createError({ statusCode: 403, message: "dev-only" });
}
const body = await readBody(event).catch(() => ({}));
const plan = body?.plan as string | undefined;
if (!plan || !(VALID_PLANS as readonly string[]).includes(plan)) {
throw createError({
statusCode: 400,
data: {
error: "INVALID_PLAN",
message: `plan must be one of: ${VALID_PLANS.join(", ")}`,
},
});
}
const db = usePrisma();
await db.profile.update({
where: { id: user.id },
data: { plan: plan as AppPlan },
});
return { success: true, plan: plan as AppPlan };
});