import type { FastifyPluginAsync } from "fastify"; import { VerificationStatus } from "@prisma/client"; import { z } from "zod"; import { slugify } from "../lib/slug.js"; const contactSchema = z.object({ type: z.enum([ "phone", "landline", "email", "instagram", "website", "whatsapp", "other", ]), value: z.string(), }); const createSchema = z.object({ title: z.string().min(1), slug: z.string().min(1).optional(), abstract: z.string().optional(), content: z.string().optional(), imageUrl: z.string().url().nullable().optional(), galleryUrls: z.array(z.string().url()).optional(), tags: z.array(z.string()).optional(), country: z.string().min(1), city: z.string().optional(), address: z.string().optional(), contacts: z.array(contactSchema).optional(), categoryIds: z.array(z.string().cuid()).optional(), authorId: z.string().cuid(), verificationStatus: z.nativeEnum(VerificationStatus).optional(), publishedAt: z.coerce.date().nullable().optional(), }); function assertNoContentImages(content: string | undefined, reply: { status: (code: number) => { send: (body: unknown) => unknown }; }) { if (content && / { app.get("/", async () => { return app.prisma.brand.findMany({ orderBy: { createdAt: "desc" }, include: { author: { select: { id: true, email: true, name: true } }, categories: { include: { category: true } }, }, }); }); app.get("/:id", async (request, reply) => { const { id } = request.params as { id: string }; const brand = await app.prisma.brand.findUnique({ where: { id }, include: { author: { select: { id: true, email: true, name: true } }, categories: { include: { category: true } }, }, }); if (!brand) { return reply.status(404).send({ error: "Brand not found" }); } return brand; }); app.post("/", async (request, reply) => { const parsed = createSchema.safeParse(request.body); if (!parsed.success) { return reply.status(400).send({ error: parsed.error.flatten() }); } const contentBlocked = assertNoContentImages(parsed.data.content, reply); if (contentBlocked) return contentBlocked; const slug = slugify(parsed.data.slug ?? parsed.data.title); const categoryIds = parsed.data.categoryIds ?? []; const galleryUrls = parsed.data.galleryUrls ?? []; const brand = await app.prisma.brand.create({ data: { title: parsed.data.title, slug, abstract: parsed.data.abstract, content: parsed.data.content ?? "", imageUrl: parsed.data.imageUrl ?? null, galleryUrls, tags: parsed.data.tags ?? [], country: parsed.data.country, city: parsed.data.city, address: parsed.data.address, contacts: parsed.data.contacts ?? [], authorId: parsed.data.authorId, verificationStatus: parsed.data.verificationStatus ?? VerificationStatus.PENDING, publishedAt: parsed.data.publishedAt ?? null, categories: { create: categoryIds.map((categoryId) => ({ categoryId })), }, }, include: { author: { select: { id: true, email: true, name: true } }, categories: { include: { category: true } }, }, }); return reply.status(201).send(brand); }); app.patch("/:id", async (request, reply) => { const { id } = request.params as { id: string }; const updateSchema = createSchema.partial().omit({ authorId: true }); const parsed = updateSchema.safeParse(request.body); if (!parsed.success) { return reply.status(400).send({ error: parsed.error.flatten() }); } const contentBlocked = assertNoContentImages(parsed.data.content, reply); if (contentBlocked) return contentBlocked; const existing = await app.prisma.brand.findUnique({ where: { id } }); if (!existing) { return reply.status(404).send({ error: "Brand not found" }); } const data = parsed.data; const categoryIds = data.categoryIds; const galleryUrls = data.galleryUrls; const brand = await app.prisma.brand.update({ where: { id }, data: { ...(data.title !== undefined ? { title: data.title } : {}), ...(data.title !== undefined || data.slug !== undefined ? { slug: slugify(data.slug ?? data.title ?? existing.title) } : {}), ...(data.abstract !== undefined ? { abstract: data.abstract } : {}), ...(data.content !== undefined ? { content: data.content } : {}), ...(data.imageUrl !== undefined ? { imageUrl: data.imageUrl } : {}), ...(galleryUrls !== undefined ? { galleryUrls } : {}), ...(data.tags !== undefined ? { tags: data.tags } : {}), ...(data.country !== undefined ? { country: data.country } : {}), ...(data.city !== undefined ? { city: data.city } : {}), ...(data.address !== undefined ? { address: data.address } : {}), ...(data.contacts !== undefined ? { contacts: data.contacts } : {}), ...(data.verificationStatus !== undefined ? { verificationStatus: data.verificationStatus } : {}), ...(data.publishedAt !== undefined ? { publishedAt: data.publishedAt } : {}), ...(categoryIds ? { categories: { deleteMany: {}, create: categoryIds.map((categoryId) => ({ categoryId })), }, } : {}), }, include: { author: { select: { id: true, email: true, name: true } }, categories: { include: { category: true } }, }, }); return brand; }); };