import type { Metadata } from "next"; import Link from "next/link"; import { db } from "@epicure/db"; import { users, recipes, userUsage, recipePhotos, ratings, reports, cookingHistory, webhooks, apiKeys } from "@epicure/db"; import { count, eq, gte, sql } from "@epicure/db"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Users, BookOpen, Sparkles, Image, History, UserPlus, ChefHat, Flag, HardDrive, Webhook, KeyRound } from "lucide-react"; import { APP_VERSION, CHANGELOG } from "@/lib/changelog"; export const metadata: Metadata = {}; export default async function AdminPage() { const currentMonth = new Date().toISOString().slice(0, 7); const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); const [ userCount, recipeCount, proUserCount, publicRecipeCount, aiUsage, imageCount, storageUsage, newUsers7d, newRecipes7d, cooked7d, pendingReports, webhookCount, apiKeyCount, ] = await Promise.all([ db.select({ count: count() }).from(users).then((r) => r[0]), db.select({ count: count() }).from(recipes).then((r) => r[0]), db.select({ count: count() }).from(users).where(eq(users.tier, "pro")).then((r) => r[0]), db.select({ count: count() }).from(recipes).where(eq(recipes.visibility, "public")).then((r) => r[0]), db .select({ total: sql`coalesce(sum(${userUsage.aiCallsUsed}), 0)` }) .from(userUsage) .where(eq(userUsage.month, currentMonth)) .then((r) => r[0]), db.select({ count: count() }).from(recipePhotos).then((r) => r[0]), Promise.all([ db.select({ total: sql`coalesce(sum(${recipePhotos.sizeMb}), 0)` }).from(recipePhotos).then((r) => r[0]), db.select({ total: sql`coalesce(sum(${ratings.photoSizeMb}), 0)` }).from(ratings).then((r) => r[0]), db.select({ total: sql`coalesce(sum(${users.avatarSizeMb}), 0)` }).from(users).then((r) => r[0]), ]).then(([photos, reviews, avatars]) => Number(photos?.total ?? 0) + Number(reviews?.total ?? 0) + Number(avatars?.total ?? 0)), db.select({ count: count() }).from(users).where(gte(users.createdAt, sevenDaysAgo)).then((r) => r[0]), db.select({ count: count() }).from(recipes).where(gte(recipes.createdAt, sevenDaysAgo)).then((r) => r[0]), db.select({ count: count() }).from(cookingHistory).where(gte(cookingHistory.cookedAt, sevenDaysAgo)).then((r) => r[0]), db.select({ count: count() }).from(reports).where(eq(reports.status, "pending")).then((r) => r[0]), db.select({ count: count() }).from(webhooks).where(eq(webhooks.active, true)).then((r) => r[0]), db.select({ count: count() }).from(apiKeys).then((r) => r[0]), ]); const stats = [ { label: "Total Users", value: userCount?.count ?? 0, sub: `${proUserCount?.count ?? 0} pro`, icon: Users }, { label: "New Users (7d)", value: newUsers7d?.count ?? 0, icon: UserPlus }, { label: "Total Recipes", value: recipeCount?.count ?? 0, sub: `${publicRecipeCount?.count ?? 0} public`, icon: BookOpen }, { label: "New Recipes (7d)", value: newRecipes7d?.count ?? 0, icon: BookOpen }, { label: "Cooked (7d)", value: cooked7d?.count ?? 0, icon: ChefHat }, { label: "AI Calls This Month", value: Number(aiUsage?.total ?? 0), icon: Sparkles }, { label: "Recipe Photos", value: imageCount?.count ?? 0, icon: Image }, { label: "Total Storage", value: storageUsage, unit: "MB", icon: HardDrive }, { label: "Pending Reports", value: pendingReports?.count ?? 0, icon: Flag, href: "/admin/reports", highlight: (pendingReports?.count ?? 0) > 0, }, { label: "Active Webhooks", value: webhookCount?.count ?? 0, icon: Webhook }, { label: "API Keys Issued", value: apiKeyCount?.count ?? 0, icon: KeyRound }, ]; return (

Overview

{stats.map(({ label, value, sub, unit, icon: Icon, href, highlight }) => { const card = ( {label}
{value.toLocaleString()}{unit ? ` ${unit}` : ""}
{sub &&

{sub}

}
); return (
{href ? {card} : card}
); })}
Version
v{APP_VERSION}

Released {CHANGELOG[0]?.date} ยท{" "} View changelog

); }