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

Aula 7: Schemas Completos do Banco - Restaurantes, Pedidos e Produtos

Módulo 3⏱️ 40 minutos📚 Avançado

🎯 Objetivo da Aula

Vamos implementar os schemas completos do banco de dados do Restaurantix: restaurantes, pedidos, produtos e itens de pedidos, com todas as relações e validações.

📋 Schemas que vamos criar

  • restaurants.ts - Tabela de restaurantes com manager
  • orders.ts - Pedidos com enum de status
  • products.ts - Produtos do cardápio
  • order-items.ts - Itens dos pedidos
  • Relações - Todas as foreign keys e relacionamentos

🏢 Schema de Restaurantes

Cada restaurante tem um manager (usuário) e pode ter vários pedidos e produtos:

// src/application/infra/db/schema/restaurants.ts
import { text, timestamp, pgTable } from "drizzle-orm/pg-core";
import { createId } from "@paralleldrive/cuid2";
import { users } from "./users";
import { relations } from "drizzle-orm";
import { orders } from "./orders";
import { products } from "./products";

export const restaurants = pgTable("restaurants", {
  id: text("id")
    .$defaultFn(() => createId())
    .primaryKey(),
  name: text("name").notNull(),
  description: text("description"),
  managerId: text("manager_id").references(() => users.id, {
    onDelete: "set null", // Se manager for deletado, restaurante fica sem manager
  }),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

// Definir relacionamentos
export const restaurantRelations = relations(restaurants, ({ one, many }) => ({
  // Um restaurante tem um manager (usuário)
  manager: one(users, {
    fields: [restaurants.managerId],
    references: [users.id],
    relationName: "restaurant_manager",
  }),
  // Um restaurante tem muitos pedidos
  orders: many(orders),
  // Um restaurante tem muitos produtos
  products: many(products),
}));

Atualizar Schema de Users

Precisamos adicionar a relação inversa no schema de usuários:

// src/application/infra/db/schema/users.ts
import { text, timestamp, pgTable, pgEnum } from "drizzle-orm/pg-core";
import { createId } from "@paralleldrive/cuid2";
import { relations } from "drizzle-orm";
import { orders } from "./orders";
import { restaurants } from "./restaurants";

export const userRoles = pgEnum("user_role", ["manager", "customer"]);

export const users = pgTable("users", {
  id: text("id")
    .$defaultFn(() => createId())
    .primaryKey(),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  phone: text("phone"),
  role: userRoles("role").default("customer").notNull(),
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const usersRelations = relations(users, ({ many, one }) => ({
  // Um manager pode gerenciar um restaurante
  managedRestaurant: one(restaurants, {
    fields: [users.id],
    references: [restaurants.managerId],
    relationName: "managed_restaurant",
  }),
  // Um customer pode fazer muitos pedidos
  orders: many(orders),
}));

📦 Schema de Pedidos

Os pedidos têm um enum de status e relacionamentos com usuários e restaurantes:

// src/application/infra/db/schema/orders.ts
import { relations } from "drizzle-orm";
import { pgTable, text, timestamp, pgEnum, integer } from "drizzle-orm/pg-core";
import { createId } from "@paralleldrive/cuid2";
import { orderItems } from "./order-items";
import { users } from "./users";
import { restaurants } from "./restaurants";

// Enum para status do pedido
export const orderStatusEnum = pgEnum("order_status", [
  "pending",    // Aguardando aprovação
  "preparing",  // Em preparo
  "ready",      // Pronto para entrega
  "delivered",  // Entregue
  "cancelled",  // Cancelado
]);

export const orders = pgTable("orders", {
  id: text("id")
    .primaryKey()
    .$defaultFn(() => createId()),
  customerId: text("customer_id").references(() => users.id, {
    onDelete: "set null", // Se customer for deletado, pedido fica sem customer
  }),
  restaurantId: text("restaurant_id")
    .notNull()
    .references(() => restaurants.id, { onDelete: "cascade" }), // Se restaurante for deletado, pedidos são deletados
  status: orderStatusEnum("status").default("pending").notNull(),
  priceInCents: integer("price_in_cents").notNull(), // Preço em centavos para evitar problemas de float
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const orderRelations = relations(orders, ({ many, one }) => ({
  // Um pedido pertence a um customer
  customer: one(users, {
    fields: [orders.customerId],
    references: [users.id],
    relationName: "order_customer",
  }),
  // Um pedido pertence a um restaurante
  restaurant: one(restaurants, {
    fields: [orders.restaurantId],
    references: [restaurants.id],
    relationName: "order_restaurant",
  }),
  // Um pedido tem muitos itens
  orderItems: many(orderItems),
}));

🍕 Schema de Produtos

Produtos pertencem a um restaurante e podem estar em vários pedidos:

// src/application/infra/db/schema/products.ts
import { text, timestamp, pgTable, integer } from "drizzle-orm/pg-core";
import { createId } from "@paralleldrive/cuid2";
import { restaurants } from "./restaurants";
import { relations } from "drizzle-orm";
import { orderItems } from "./order-items";

export const products = pgTable("products", {
  id: text("id")
    .$defaultFn(() => createId())
    .primaryKey(),
  name: text("name").notNull(),
  description: text("description"),
  priceInCents: integer("price_in_cents").notNull(), // Preço em centavos
  restaurantId: text("restaurant_id")
    .notNull()
    .references(() => restaurants.id, { onDelete: "cascade" }), // Se restaurante for deletado, produtos são deletados
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const productRelations = relations(products, ({ one, many }) => ({
  // Um produto pertence a um restaurante
  restaurant: one(restaurants, {
    fields: [products.restaurantId],
    references: [restaurants.id],
    relationName: "product_restaurant",
  }),
  // Um produto pode estar em muitos itens de pedidos
  orderItems: many(orderItems),
}));

📋 Schema de Itens de Pedidos

Tabela de junção entre pedidos e produtos, com quantidade e preço:

// src/application/infra/db/schema/order-items.ts
import { relations } from "drizzle-orm";
import { createId } from "@paralleldrive/cuid2";
import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
import { orders } from "./orders";
import { products } from "./products";

export const orderItems = pgTable("order_items", {
  id: text("id")
    .primaryKey()
    .$defaultFn(() => createId()),
  orderId: text("order_id")
    .notNull()
    .references(() => orders.id, { onDelete: "cascade" }), // Se pedido for deletado, itens são deletados
  productId: text("product_id")
    .notNull()
    .references(() => products.id, { onDelete: "set null" }), // Se produto for deletado, item fica sem produto
  priceInCents: integer("price_in_cents").notNull(), // Preço do produto no momento do pedido
  quantity: integer("quantity").notNull(), // Quantidade do produto
  createdAt: timestamp("created_at").notNull().defaultNow(),
  updatedAt: timestamp("updated_at").notNull().defaultNow(),
});

export const orderItemRelations = relations(orderItems, ({ one }) => ({
  // Um item pertence a um pedido
  order: one(orders, {
    fields: [orderItems.orderId],
    references: [orders.id],
    relationName: "order_item_order",
  }),
  // Um item referencia um produto
  product: one(products, {
    fields: [orderItems.productId],
    references: [products.id],
    relationName: "order_item_product",
  }),
}));

📁 Arquivo de Índice

Vamos atualizar o arquivo de índice para exportar todos os schemas:

// src/application/infra/db/schema/index.ts
export * from "./auth-links";
export * from "./order-items";
export * from "./orders";
export * from "./products";
export * from "./restaurants";
export * from "./users";

🔄 Executar Migrações

Agora vamos gerar e executar as migrações para criar as tabelas:

# Gerar migração
bun run drizzle-kit generate

# Executar migração
bun run drizzle-kit migrate

# Verificar se as tabelas foram criadas
bun run drizzle-kit studio

Estrutura Final do Banco

📊 ESTRUTURA DO BANCO DE DADOS

┌─ users (usuários)
│  ├─ id (PK)
│  ├─ name, email, phone
│  ├─ role (manager | customer)
│  └─ timestamps
│
├─ restaurants (restaurantes)
│  ├─ id (PK)
│  ├─ name, description
│  ├─ managerId (FK → users.id)
│  └─ timestamps
│
├─ products (produtos)
│  ├─ id (PK)
│  ├─ name, description, priceInCents
│  ├─ restaurantId (FK → restaurants.id)
│  └─ timestamps
│
├─ orders (pedidos)
│  ├─ id (PK)
│  ├─ customerId (FK → users.id)
│  ├─ restaurantId (FK → restaurants.id)
│  ├─ status (pending|preparing|ready|delivered|cancelled)
│  ├─ priceInCents
│  └─ timestamps
│
├─ order_items (itens dos pedidos)
│  ├─ id (PK)
│  ├─ orderId (FK → orders.id)
│  ├─ productId (FK → products.id)
│  ├─ quantity, priceInCents
│  └─ timestamps
│
└─ auth_links (links de autenticação)
   ├─ id (PK)
   ├─ userId (FK → users.id)
   ├─ code, expiresAt
   └─ timestamps

🎯 Próximos Passos

Na próxima aula vamos implementar as rotas de restaurantes e começar a usar esses schemas para criar e gerenciar restaurantes.

  • Rota para adicionar restaurante + manager
  • Rota para obter restaurante gerenciado
  • Validações e tratamento de erros
  • Testes das funcionalidades