From eb871073f2b7faf66938d4207c2198253cf0c88c Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Mon, 11 May 2026 15:44:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(backend):=20=5F=5FDEV=5F=5F=20/api/dev/set?= =?UTF-8?q?-plan=20=E2=80=94=20user=20sets=20own=20plan=20(non-prod=20only?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/server/api/dev/set-plan.post.ts | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 backend/server/api/dev/set-plan.post.ts diff --git a/backend/server/api/dev/set-plan.post.ts b/backend/server/api/dev/set-plan.post.ts new file mode 100644 index 0000000..ef51963 --- /dev/null +++ b/backend/server/api/dev/set-plan.post.ts @@ -0,0 +1,47 @@ +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 }; +});