fix: recipe/storage tier limits are lifetime totals, not monthly (v0.58.0)
Recipe count and storage usage shared the monthly user_usage bucket with AI calls, so both incorrectly reset every month even though nothing was deleted. Only AI calls should be monthly. Recipe count and storage are now derived live from real data (recipes, recipe/review photos, avatar) instead of a counter — deleting a photo or recipe is itself the "decrement", no extra wiring needed. Storage size is tracked per-row (recipePhotos.sizeMb, ratings.photoSizeMb, users.avatarSizeMb) and threaded through presign -> upload -> save. Also fixes avatar removal silently no-oping (client sent a field the PATCH schema didn't recognize). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { requireSessionOrApiKey } from "@/lib/api-auth";
|
||||
import { applyRateLimit } from "@/lib/rate-limit";
|
||||
import { getDefaultProviderWithKey } from "@/lib/ai/resolve-user-key";
|
||||
import { withAiQuota, resolveAiConfigOrError } from "@/lib/ai/ai-error";
|
||||
import { checkAndIncrementTierLimit, incrementUsage, TierLimitError } from "@/lib/tiers";
|
||||
import { checkAndIncrementTierLimit, TierLimitError } from "@/lib/tiers";
|
||||
import { generateMeal, MEAL_COURSES } from "@/lib/ai/features/generate-meal";
|
||||
import { getUserPrivateBio } from "@/lib/ai/user-bio";
|
||||
import { getMessages } from "@/lib/i18n/server";
|
||||
@@ -59,19 +59,12 @@ export async function POST(req: NextRequest) {
|
||||
if (!result.ok) return result.response;
|
||||
const meal = result.data;
|
||||
|
||||
// Each recipe in the meal creates a real recipe row — charge the recipe
|
||||
// limit for all of them before inserting anything, refunding on breach
|
||||
// so a rejected meal doesn't consume quota (same pattern as meal-plan
|
||||
// generation).
|
||||
let chargedRecipes = 0;
|
||||
// Each recipe in the meal creates a real recipe row — check the recipe
|
||||
// limit covers all of them before inserting anything.
|
||||
try {
|
||||
for (let i = 0; i < meal.recipes.length; i++) {
|
||||
await checkAndIncrementTierLimit(userId, tier, "recipe");
|
||||
chargedRecipes++;
|
||||
}
|
||||
await checkAndIncrementTierLimit(userId, tier, "recipe", meal.recipes.length);
|
||||
} catch (err) {
|
||||
if (err instanceof TierLimitError) {
|
||||
if (chargedRecipes > 0) await incrementUsage(userId, "recipe", -chargedRecipes);
|
||||
return NextResponse.json({ error: "Recipe limit reached for your tier" }, { status: 403 });
|
||||
}
|
||||
throw err;
|
||||
|
||||
Reference in New Issue
Block a user