import type { FastifyPluginAsync } from "fastify"; import { z } from "zod"; import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; import { generateUniqueRestaurantSlug } from "../lib/slug.js"; import { supabase } from "../lib/supabase.js"; const createRestaurantSchema = z.object({ name: z.string().min(1).max(120), logoUrl: z.string().nullish(), phone: z.string().max(30).nullish(), address: z.string().max(500).nullish(), }); const updateRestaurantSchema = createRestaurantSchema.partial(); export const restaurantsRoutes: FastifyPluginAsync = async (app) => { app.post("/restaurants", async (req, reply) => { const userId = await requireAuth(req, reply); if (!userId || !supabase) return; const parsed = createRestaurantSchema.safeParse(req.body); if (!parsed.success) { return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); } const { name, logoUrl, phone, address } = parsed.data; const slug = await generateUniqueRestaurantSlug(name); const { data: restaurant, error: restaurantError } = await supabase .from("restaurants") .insert({ name, slug, logo_url: logoUrl ?? null, phone: phone ?? null, address: address ?? null, created_by: userId, }) .select() .single(); if (restaurantError || !restaurant) { req.log.error(restaurantError); return reply.code(500).send({ message: "failed to create restaurant" }); } const { error: memberError } = await supabase .from("restaurant_members") .insert({ restaurant_id: restaurant.id, user_id: userId, role: "owner" }); const { data: location, error: locationError } = await supabase .from("locations") .insert({ restaurant_id: restaurant.id, name: "Ana Şube" }) .select() .single(); if (memberError || locationError || !location) { req.log.error(memberError ?? locationError); return reply.code(500).send({ message: "failed to finish restaurant setup" }); } const { data: menu, error: menuError } = await supabase .from("menus") .insert({ location_id: location.id, name: "Menüm" }) .select() .single(); if (menuError || !menu) { req.log.error(menuError); return reply.code(500).send({ message: "failed to create default menu" }); } return reply.code(201).send({ restaurant, location, menu }); }); app.get("/restaurants/:id", async (req, reply) => { const userId = await requireAuth(req, reply); if (!userId || !supabase) return; const { id } = req.params as { id: string }; const isMember = await requireRestaurantMember(userId, id); if (!isMember) return reply.code(403).send({ message: "forbidden" }); const { data, error } = await supabase.from("restaurants").select().eq("id", id).single(); if (error || !data) return reply.code(404).send({ message: "not found" }); return reply.send(data); }); app.patch("/restaurants/:id", async (req, reply) => { const userId = await requireAuth(req, reply); if (!userId || !supabase) return; const { id } = req.params as { id: string }; const isMember = await requireRestaurantMember(userId, id); if (!isMember) return reply.code(403).send({ message: "forbidden" }); const parsed = updateRestaurantSchema.safeParse(req.body); if (!parsed.success) { return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); } const { name, logoUrl, phone, address } = parsed.data; const { data, error } = await supabase .from("restaurants") .update({ ...(name !== undefined ? { name } : {}), ...(logoUrl !== undefined ? { logo_url: logoUrl } : {}), ...(phone !== undefined ? { phone } : {}), ...(address !== undefined ? { address } : {}), updated_at: new Date().toISOString(), }) .eq("id", id) .select() .single(); if (error || !data) { req.log.error(error); return reply.code(500).send({ message: "failed to update restaurant" }); } return reply.send(data); }); app.get("/restaurants/:id/theme", async (req, reply) => { const userId = await requireAuth(req, reply); if (!userId || !supabase) return; const { id } = req.params as { id: string }; const isMember = await requireRestaurantMember(userId, id); if (!isMember) return reply.code(403).send({ message: "forbidden" }); const { data: themeRow } = await supabase .from("restaurant_themes") .select("theme_id, overrides, themes(key, name, config)") .eq("restaurant_id", id) .maybeSingle(); return reply.send({ themeKey: (themeRow?.themes as unknown as { key?: string } | null)?.key ?? "elegant", overrides: themeRow?.overrides ?? {}, }); }); app.put("/restaurants/:id/theme", async (req, reply) => { const userId = await requireAuth(req, reply); if (!userId || !supabase) return; const { id } = req.params as { id: string }; const isMember = await requireRestaurantMember(userId, id); if (!isMember) return reply.code(403).send({ message: "forbidden" }); const { themeKey } = (req.body as { themeKey?: string }) ?? {}; if (!themeKey) { return reply.code(400).send({ message: "themeKey is required" }); } const { data: themeData } = await supabase .from("themes") .select("id") .eq("key", themeKey) .maybeSingle(); if (!themeData) { return reply.code(404).send({ message: "Theme not found" }); } const { error } = await supabase.from("restaurant_themes").upsert( { restaurant_id: id, theme_id: themeData.id, }, { onConflict: "restaurant_id" }, ); if (error) { req.log.error(error); return reply.code(500).send({ message: "failed to update theme" }); } return reply.send({ success: true, themeKey }); }); };