Files
menulio/apps/api/src/routes/restaurants.ts
T
2026-08-20 01:51:59 +03:00

184 lines
5.9 KiB
TypeScript

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 });
});
};