import { QueryCtx, MutationCtx } from "../_generated/server"; import type { Id, Doc } from "../_generated/dataModel"; /** * Recalculate product averageRating and reviewCount from approved reviews. * Call after review approve/delete. */ export async function recalculateProductRating( ctx: MutationCtx, productId: Id<"products">, ) { const approved = await ctx.db .query("reviews") .withIndex("by_product_approved", (q) => q.eq("productId", productId).eq("isApproved", true), ) .collect(); const count = approved.length; const averageRating = count > 0 ? approved.reduce((sum, r) => sum + r.rating, 0) / count : undefined; const reviewCount = count > 0 ? count : undefined; await ctx.db.patch(productId, { averageRating, reviewCount, updatedAt: Date.now(), }); } export async function getProductWithRelations( ctx: QueryCtx, productId: Id<"products">, ) { const product = await ctx.db.get(productId); if (!product) return null; const [imagesRaw, variants, category] = await Promise.all([ ctx.db .query("productImages") .withIndex("by_product", (q) => q.eq("productId", productId)) .collect(), ctx.db .query("productVariants") .withIndex("by_product_and_active", (q) => q.eq("productId", productId).eq("isActive", true), ) .collect(), ctx.db.get(product.categoryId), ]); const images = imagesRaw.sort((a, b) => a.position - b.position); return { ...product, images, variants, category }; } export async function enrichProducts( ctx: QueryCtx, products: Doc<"products">[], ) { return Promise.all( products.map(async (product) => { const [imagesRaw, variants] = await Promise.all([ ctx.db .query("productImages") .withIndex("by_product", (q) => q.eq("productId", product._id)) .collect(), ctx.db .query("productVariants") .withIndex("by_product_and_active", (q) => q.eq("productId", product._id).eq("isActive", true), ) .collect(), ]); const images = imagesRaw.sort((a, b) => a.position - b.position); return { ...product, images, variants }; }), ); }