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:
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { db, users, recipes, userUsage, eq, desc, count, and, sql } from "@epicure/db";
|
||||
import { db, users, recipes, userUsage, eq, desc, count, and } from "@epicure/db";
|
||||
import { getStorageUsedMb } from "@/lib/tiers";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@@ -42,10 +43,10 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
.where(and(eq(userUsage.userId, id), eq(userUsage.month, currentMonth)))
|
||||
.limit(1);
|
||||
|
||||
const [recipeCountRow] = await db
|
||||
.select({ count: count() })
|
||||
.from(recipes)
|
||||
.where(eq(recipes.authorId, id));
|
||||
const [[recipeCountRow], storageUsedMb] = await Promise.all([
|
||||
db.select({ count: count() }).from(recipes).where(eq(recipes.authorId, id)),
|
||||
getStorageUsedMb(id),
|
||||
]);
|
||||
|
||||
const recentRecipes = await db
|
||||
.select({
|
||||
@@ -105,11 +106,11 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Usage This Month ({currentMonth})</CardTitle>
|
||||
<CardTitle className="text-base">Usage & Limits</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">AI Calls Used</span>
|
||||
<span className="text-muted-foreground">AI Calls Used ({currentMonth})</span>
|
||||
<span className="font-medium">{usage?.aiCallsUsed ?? 0}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
@@ -118,7 +119,7 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Storage Used</span>
|
||||
<span className="font-medium">{usage?.storageUsedMb ?? 0} MB</span>
|
||||
<span className="font-medium">{storageUsedMb} MB</span>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<ResetUsageButton userId={user.id} />
|
||||
|
||||
Reference in New Issue
Block a user