🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →

Aula 8: Rotas de Restaurantes - Criar e Gerenciar

Módulo 3⏱️ 35 minutos📚 Intermediário

🎯 Objetivo da Aula

Vamos implementar as rotas para criar restaurantes e gerenciar restaurantes existentes, usando os schemas que criamos na aula anterior.

📋 Rotas que vamos criar

  • POST /restaurants - Criar restaurante + manager
  • GET /managed-restaurant - Obter restaurante gerenciado
  • Validações e tratamento de erros
  • Testes das funcionalidades

🏢 Rota: Adicionar Restaurante

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

Pontos Importantes

🔍 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: Obter Restaurante Gerenciado

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

Middleware de Autenticação Atualizado

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

🔄 Atualizar Rota de Autenticação

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

📝 Registrar Rotas no Servidor

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

🧪 Testando as Rotas

1. Teste da Criação de Restaurante

# 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"
}

2. Teste do Login e Obtenção do Restaurante

# 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 Completo

🔄 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

🎯 Próximos Passos

Na próxima aula vamos implementar as rotas de gerenciamento de pedidos: detalhes, aprovação, despacho, entrega e cancelamento.

  • Rota para obter detalhes de um pedido
  • Rotas para mudança de status dos pedidos
  • Validações de transições de status
  • Joins complexos com múltiplas tabelas