126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
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<typeof checkoutSchema>, 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<typeof productSchema>, 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 } });
|
|
}
|