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:
@@ -20,6 +20,9 @@ const PatchSchema = z.object({
|
||||
// another user's object. `null` reverts to the initials fallback (or
|
||||
// Gravatar, if useGravatar is on — see below).
|
||||
avatarKey: z.string().max(500).optional().nullable(),
|
||||
// Size (MB, rounded up) of the file behind avatarKey — as returned by
|
||||
// /api/v1/upload/avatar-presign. Ignored unless avatarKey is set.
|
||||
avatarSizeMb: z.number().int().min(0).max(50).default(0),
|
||||
// Off by default (Settings → Profile) — Gravatar is looked up by an MD5
|
||||
// hash of the user's email, sent to a third party.
|
||||
useGravatar: z.boolean().optional(),
|
||||
@@ -36,7 +39,7 @@ export async function PATCH(req: Request) {
|
||||
return NextResponse.json({ error: "Username already taken" }, { status: 409 });
|
||||
}
|
||||
|
||||
const { avatarKey, useGravatar, ...rest } = body.data;
|
||||
const { avatarKey, avatarSizeMb, useGravatar, ...rest } = body.data;
|
||||
const updates: Partial<typeof users.$inferInsert> = { ...rest };
|
||||
|
||||
// useGravatar is only ever toggled from the settings form, never alongside
|
||||
@@ -54,12 +57,14 @@ export async function PATCH(req: Request) {
|
||||
const gravatarOptedIn = useGravatar ?? (await getUseGravatar(session.user.id));
|
||||
updates.avatarUrl = gravatarOptedIn ? gravatarUrl(session.user.email) : null;
|
||||
updates.hasCustomAvatar = false;
|
||||
updates.avatarSizeMb = 0;
|
||||
} else {
|
||||
if (!isOwnedAvatarKey(avatarKey, session.user.id)) {
|
||||
return NextResponse.json({ error: "Invalid avatar key" }, { status: 400 });
|
||||
}
|
||||
updates.avatarUrl = getPublicUrl(avatarKey);
|
||||
updates.hasCustomAvatar = true;
|
||||
updates.avatarSizeMb = avatarSizeMb;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user