184 lines
5.9 KiB
TypeScript
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 });
|
|
});
|
|
};
|