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.