import 'server-only'; import { z } from 'zod'; import { db } from './db'; import { getPayments } from './payments'; import { validateLines, type CartLine } from '@/lib/cart'; export const cartLineSchema = z.object({ productId: z.string().min(1), variant: z.string().max(60).optional(), quantity: z.number().int().min(1).max(99), }); export const checkoutSchema = z.object({ email: z.string().email(), address: z.string().min(5).max(300), lines: z.array(cartLineSchema).min(1).max(50), }); export async function listCategories() { return db.category.findMany({ orderBy: { name: 'asc' } }); } export type ProductQuery = { q?: string; category?: string; sort?: string; page?: number; pageSize?: number }; export async function listProducts({ q, category, sort, page = 1, pageSize = 9 }: ProductQuery) { const where = { active: true, ...(category ? { category: { slug: category } } : {}), ...(q ? { OR: [{ name: { contains: q } }, { description: { contains: q } }] } : {}), }; const orderBy = sort === 'price-asc' ? { price: 'asc' as const } : sort === 'price-desc' ? { price: 'desc' as const } : { createdAt: 'desc' as const }; const [items, total] = await Promise.all([ db.product.findMany({ where, orderBy, skip: (page - 1) * pageSize, take: pageSize, include: { category: true } }), db.product.count({ where }), ]); return { items, total, page, pageSize, pages: Math.max(1, Math.ceil(total / pageSize)) }; } export function getProductBySlug(slug: string) { return db.product.findUnique({ where: { slug }, include: { category: true } }); } export async function createOrder(input: z.infer, userId?: string | null) { const ids = [...new Set(input.lines.map((l) => l.productId))]; const products = await db.product.findMany({ where: { id: { in: ids } } }); const result = validateLines(input.lines as CartLine[], products); if (!result.ok) return result; const provider = getPayments(); const providerRef = `${provider.name}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; const order = await db.order.create({ data: { userId: userId ?? null, email: input.email.toLowerCase(), address: input.address, total: result.total, currency: 'USD', status: provider.name === 'mock' ? 'paid' : 'pending', provider: provider.name, providerRef, items: { create: result.items.map((i) => ({ productId: i.productId, name: i.name, variant: i.variant ?? null, price: i.price, quantity: i.quantity, })), }, }, include: { items: true }, }); // Decrement inventory (server-side, after a validated order). for (const item of result.items) { await db.product.update({ where: { id: item.productId }, data: { stock: { decrement: item.quantity } } }); } return { ok: true as const, order }; } export function listOrdersForUser(userId: string) { return db.order.findMany({ where: { userId }, include: { items: true }, orderBy: { createdAt: 'desc' } }); } export function listAllOrders() { return db.order.findMany({ include: { items: true }, orderBy: { createdAt: 'desc' }, take: 100 }); } export function updateOrderStatus(id: string, status: string) { return db.order.update({ where: { id }, data: { status } }); } export function adminListProducts() { return db.product.findMany({ include: { category: true }, orderBy: { createdAt: 'desc' } }); } export const productSchema = z.object({ name: z.string().min(1).max(160), slug: z.string().min(1).max(160).regex(/^[a-z0-9-]+$/), description: z.string().max(2000).optional().or(z.literal('')), price: z.number().int().min(0), stock: z.number().int().min(0), categoryId: z.string().optional().nullable(), active: z.boolean().optional(), }); export async function upsertProduct(data: z.infer, id?: string) { const payload = { name: data.name, slug: data.slug, description: data.description || '', price: data.price, stock: data.stock, categoryId: data.categoryId || null, active: data.active ?? true, }; if (id) return db.product.update({ where: { id }, data: payload }); return db.product.create({ data: payload }); } export function deleteProduct(id: string) { return db.product.delete({ where: { id } }); }