2f3ba14093
Admins can now disable specific AI features per tier from Admin > Tier Limits — new feature_flags table (feature x tier -> enabled, defaulting to true so adding a new gated feature never needs a backfill). Covers recipe variations, drink pairing, and meal pairing to start. When disabled for a user's tier, the button stays visible (with a small lock badge) but opens an upgrade dialog instead of running; the API route rejects the call server-side either way (requireFeatureEnabled, re-reads tier from the DB rather than trusting the session's cache, same rationale as checkAndIncrementTierLimit). The upgrade dialog is informational only — no Stripe checkout exists yet (STRIPE_PLAN.md is still just a plan) — its CTA links to /support prefilled as an upgrade-interest suggestion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
39 lines
1.2 KiB
TypeScript
39 lines
1.2 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { requireAdmin } from "@/lib/api-auth";
|
|
import { getFeatureFlagMatrix, setFeatureFlag, FEATURE_KEYS, TIERS } from "@/lib/feature-flags";
|
|
|
|
export async function GET() {
|
|
const { response } = await requireAdmin();
|
|
if (response) return response;
|
|
|
|
const matrix = await getFeatureFlagMatrix();
|
|
return NextResponse.json(matrix);
|
|
}
|
|
|
|
const UpdateBody = z.object({
|
|
featureKey: z.enum(FEATURE_KEYS as [string, ...string[]]),
|
|
tier: z.enum(TIERS as [string, ...string[]]),
|
|
enabled: z.boolean(),
|
|
});
|
|
|
|
export async function PATCH(req: NextRequest) {
|
|
const { session, response } = await requireAdmin();
|
|
if (response) return response;
|
|
|
|
const body = (await req.json()) as unknown;
|
|
const parsed = UpdateBody.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json({ error: "Validation error", issues: parsed.error.issues }, { status: 400 });
|
|
}
|
|
|
|
await setFeatureFlag(
|
|
parsed.data.featureKey as (typeof FEATURE_KEYS)[number],
|
|
parsed.data.tier as (typeof TIERS)[number],
|
|
parsed.data.enabled,
|
|
session!.user.id
|
|
);
|
|
|
|
return NextResponse.json({ ok: true });
|
|
}
|