Vamos implementar as rotas para criar restaurantes e gerenciar restaurantes existentes, usando os schemas que criamos na aula anterior.
POST /restaurants
- Criar restaurante + managerGET /managed-restaurant
- Obter restaurante gerenciadoEsta rota cria um manager e um restaurante em uma única transação:
// src/http/routes/add-restaurant.ts import { t } from "elysia"; import { db } from "@/application/infra/db/connection"; import { restaurants, users } from "@/application/infra/db/schema"; import { csrf } from "../csrf"; export const addRestaurant = csrf.post( "/restaurants", async ({ body, set }) => { const { restaurantName, managerName, email, phone } = body; try { // 1. Criar o manager primeiro const [manager] = await db .insert(users) .values({ name: managerName, email, phone, role: "manager", // Definir como manager }) .returning({ id: users.id }); if (!manager) { set.status = 400; return { error: "Failed to create manager" }; export default } // 2. Criar o restaurante com o manager await db.insert(restaurants).values({ name: restaurantName, managerId: manager.id }); // 3. Retornar sucesso (204 No Content) set.status = 204; } catch (error) { console.error("Error creating restaurant:", error); set.status = 500; return { error: "Internal server error" }; } }, { // Validação do body com TypeBox body: t.Object({ restaurantName: t.String({ minLength: 1 }), managerName: t.String({ minLength: 1 }), email: t.String({ format: "email" }), phone: t.String({ minLength: 1 }) }) }, );
🔍 ANÁLISE DA ROTA add-restaurant ✅ Proteção CSRF - Usa middleware csrf para proteção ✅ Validação TypeBox - Valida todos os campos obrigatórios - Email com formato correto ✅ Transação Implícita - Cria manager primeiro - Depois cria restaurante - Se falhar, rollback automático ✅ Tratamento de Erros - Try/catch para capturar erros - Status codes apropriados - Logs para debugging ✅ Returning Clause - Retorna apenas o ID do manager - Otimização de performance
Rota protegida que retorna o restaurante do manager autenticado:
// src/http/routes/get-managed-restaurant.ts import { db } from "@/application/infra/db/connection"; import { auth } from "../auth"; import { eq } from "drizzle-orm"; import { restaurants } from "@/application/infra/db/schema"; export const getManagedRestaurant = auth.get( "/managed-restaurant", async ({ getCurrentUser }) => { // 1. Obter dados do usuário autenticado const { restaurantId } = await getCurrentUser(); // 2. Verificar se é um manager if (!restaurantId) { throw new Error("User is not a manager"); } // 3. Buscar dados do restaurante const [restaurant] = await db .select() .from(restaurants) .where(eq(restaurants.id, restaurantId)); // 4. Verificar se restaurante existe if (!restaurant) { throw new Error("Restaurant not found"); } // 5. Retornar dados do restaurante return restaurant; }, );
Precisamos atualizar o middleware de auth para incluir o restaurantId:
// src/http/auth.ts (atualização) import { Elysia } from "elysia"; import { jwt } from "@elysiajs/jwt"; import { cookie } from "@elysiajs/cookie"; export const auth = new Elysia({ name: "auth" }) .use( jwt({ name: "jwt", secret: process.env.JWT_SECRET || "your-secret-key" }) ) .use(cookie()) .derive(({ jwt, cookie }) => { return { getCurrentUser: async () => { const token = cookie.auth; if (!token) { throw new Error("Unauthorized"); } const payload = await jwt.verify(token); if (!payload) { throw new Error("Unauthorized"); } // Retorna sub (userId) e restaurantId se existir return payload as { sub: string; restaurantId?: string; }; }, signUser: async (payload: { sub: string; restaurantId?: string }) => { return await jwt.sign(payload); } }; }) .macro(({ onBeforeHandle }) => ({ isSignedIn: (enabled: boolean = true) => { if (!enabled) return; onBeforeHandle(async ({ getCurrentUser, error }) => { try { await getCurrentUser(); } catch { return error(401, "Unauthorized"); } }); } }));
Precisamos atualizar a rota de autenticação para incluir o restaurantId no JWT:
// src/http/routes/authenticate-from-link.ts (atualização) import { t } from "elysia"; import { db } from "@/application/infra/db/connection"; import dayjs from "dayjs"; import { auth } from "../auth"; import { eq } from "drizzle-orm"; import { authLinks, restaurants } from "@/application/infra/db/schema"; import { UnauthorizedError } from "../errors/unauthorized-error"; export const authenticateFromLink = auth.get( "/auth-links/authenticate", async ({ query, set, signUser }) => { const { code, redirect } = query; // 1. Buscar link de autenticação const [authLinkFromCode] = await db .select() .from(authLinks) .where(eq(authLinks.code, code)); if (!authLinkFromCode) { throw new UnauthorizedError(); } // 2. Verificar se não expirou const isExpired = dayjs().isAfter(dayjs(authLinkFromCode.expiresAt)); if (isExpired) { await db.delete(authLinks).where(eq(authLinks.code, code)); throw new UnauthorizedError(); } // 3. Buscar restaurante gerenciado (se for manager) const [managedRestaurant] = await db .select() .from(restaurants) .where(eq(restaurants.managerId, authLinkFromCode.userId)); // 4. Gerar JWT com restaurantId se for manager await signUser({ sub: authLinkFromCode.userId, restaurantId: managedRestaurant?.id, // Incluir restaurantId se existir }); // 5. Remover link usado e redirecionar await db.delete(authLinks).where(eq(authLinks.code, code)); set.headers.location = redirect; }, { query: t.Object({ code: t.String(), redirect: t.String() }) }, );
Vamos adicionar as novas rotas no servidor principal:
// src/http/server.ts (atualização) import { addRestaurant } from "./routes/add-restaurant"; import { getManagedRestaurant } from "./routes/get-managed-restaurant"; import { Metadata } from 'next'; export const metadata: Metadata = { title: 'Aula 8 | CrazyStack', description: 'Curso completo sobre aula 8. Aprenda técnicas práticas e melhores práticas para desenvolvimento moderno. Guia atualizado 2025.', keywords: ['aula', '8', 'programação', 'desenvolvimento', 'tutorial', 'crazystack'], openGraph: { title: 'Aula 8', description: 'Curso completo sobre aula 8. Aprenda técnicas práticas e melhores práticas para desenvolvimento moderno. Guia atualizado 2025.', type: 'article' } // ... outras importações const app = new Elysia() .use(cors()) .use(csrf) .use(auth) .use(getCsrfToken) .use(addRestaurant) // ← Nova rota .use(getManagedRestaurant) // ← Nova rota .use(sendAuthLink) .use(authenticateFromLink) .use(signOut) .use(getProfile) // ... outras rotas
# 1. Obter token CSRF GET http://localhost:3000/csrf-token # 2. Criar restaurante POST http://localhost:3000/restaurants Content-Type: application/json X-CSRF-Token: [token_obtido] { "restaurantName": "Pizzaria do João", "managerName": "João Silva", "email": "joao@pizzaria.com", "phone": "(11) 99999-9999" }
# 1. Enviar link de autenticação POST http://localhost:3000/auth-links/authenticate Content-Type: application/json X-CSRF-Token: [token_csrf] { "email": "joao@pizzaria.com" } # 2. Usar link do email para autenticar GET http://localhost:3000/auth-links/authenticate?code=[codigo]&redirect=http://localhost:3000 # 3. Obter restaurante gerenciado (com cookie de auth) GET http://localhost:3000/managed-restaurant
🔄 FLUXO DE CRIAÇÃO E GERENCIAMENTO 1️⃣ CRIAÇÃO DO RESTAURANTE ├─ Frontend obtém CSRF token ├─ Envia dados do restaurante + manager ├─ Backend cria user com role "manager" ├─ Backend cria restaurant com managerId └─ Retorna 204 (sucesso) 2️⃣ AUTENTICAÇÃO DO MANAGER ├─ Manager solicita magic link ├─ Backend envia email com código ├─ Manager clica no link ├─ Backend valida código ├─ Backend busca restaurante do manager ├─ Backend gera JWT com restaurantId └─ Manager é redirecionado autenticado 3️⃣ GERENCIAMENTO DO RESTAURANTE ├─ Frontend faz requests com cookie auth ├─ Middleware extrai restaurantId do JWT ├─ Rotas usam restaurantId para filtrar dados └─ Manager só vê dados do seu restaurante
Na próxima aula vamos implementar as rotas de gerenciamento de pedidos: detalhes, aprovação, despacho, entrega e cancelamento.