404 Not Found

404 Not Found


nginx

Exercício Completar — Funcionalidades Essenciais

Na Fase 2, aprendemos sobre Pinia, Composable, Server API, Middleware e Plugins — agora é hora de juntar tudo. Os requisitos de Charlie são: o carrinho de compras deve ser totalmente integrado da API através da Store até a página; o painel de administração de Bob deve ter controles de acesso; e a experiência de compra de Alice deve ser fluida e ininterrupta.

1. O Que Você Vai Aprender


2. Uma História Real Sobre uma Equipe

(1) Ponto Crítico: Módulos operam de forma independente, mas não conseguem se integrar

A página do carrinho de compras de Alice exibe produtos, mas os dados são perdidos após atualizar. A API de Bob está escrita, mas o frontend não a está chamando. O middleware foi escrito, mas não foi integrado. Cada módulo funciona bem por si só, mas quando colocados juntos, o sistema falha.

(2) Solução com Teste de Integração Ponta a Ponta

Integrar completamente a cadeia de dados da API → Composable → Store → Página, garantindo um fluxo claro de dados em cada etapa.

(3) Benefícios: Uma Experiência deCompra Completa

Alice pode adicionar itens ao carrinho → os itens são mantidos após atualizar → prosseguir para a página de checkout → o painel administrativo é protegido por controles de acesso; todo o processo é fluido.


3. Arquitetura Colaborativa dos Módulos Funcionais Essenciais da Fase 2

100%
graph TB
    A[MegaShop Fase 2] --> B[Camada Server API]
    A --> C[Camada Composable]
    A --> D[Camada de Estado - Pinia]
    A --> E[Camada de Middleware]
    A --> F[Camada de Plugin]

    B --> B1[/api/products CRUD]
    B --> B2[/api/cart CRUD]
    B --> B3[/api/auth Login/Register]

    C --> C1[useCart → Store + API]
    C --> C2[useProductFilter → API]
    C --> C3[usePriceFormat → Puro]

    D --> D1[cartStore → items/total]
    D --> D2[userStore → auth/profile]

    E --> E1[auth.global.ts → Verificação de login]
    E --> E2[admin.ts → Verificação de papel]

    F --> F1[logger → Debug]
    F --> F2[stripe.client → Pagamento]

    C1 --> D1
    C1 --> B2
    C2 --> B1

(1) Caminho Completo do Fluxo de Dados

Operação Fluxo de Dados Módulos Envolvidos
Navegar Produtos API → useFetch → Página Server API
Adicionar ao Carrinho API → useCart → cartStore → Página API + Composable + Store
Ver Carrinho cartStore → Página Store
Checkout cartStore → useCheckout → Stripe → API Store + Composable + Plugin + API
Painel Admin auth middleware → admin middleware → página Middleware

4. API Completa do Carrinho de Compras

(1) ▶Exemplo: API Completa do Carrinho de Compras

TYPESCRIPT
// server/api/cart/index.get.ts
export default defineEventHandler((event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const cart = mockCarts.find(c => c.sessionId === sessionId)
  return cart || { sessionId, items: [], total: 0 }
})

Saída:

TEXT
// Execução Bem-sucedida
TYPESCRIPT
// server/api/cart/add.post.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId, quantity = 1 } = await readBody(event)

  const product = mockProducts.find(p => p.id === productId)
  if (!product) throw createError({ statusCode: 404, message: 'Product not found' })

  let cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) {
    cart = { sessionId, items: [], total: 0 }
    mockCarts.push(cart)
  }

  const existing = cart.items.find(i => i.productId === productId)
  if (existing) {
    existing.quantity += quantity
  } else {
    cart.items.push({ productId, name: product.name, price: product.price, quantity, image: product.image })
  }
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  setCookie(event, 'session-id', sessionId, { httpOnly: true, maxAge: 86400 * 30 })
  return cart
})
TYPESCRIPT
// server/api/cart/remove.delete.ts
export default defineEventHandler(async (event) => {
  const sessionId = getCookie(event, 'session-id') || 'default'
  const { productId } = await readBody(event)

  const cart = mockCarts.find(c => c.sessionId === sessionId)
  if (!cart) throw createError({ statusCode: 404, message: 'Cart not found' })

  cart.items = cart.items.filter(i => i.productId !== productId)
  cart.total = cart.items.reduce((s, i) => s + i.price * i.quantity, 0)
  return cart
})

5. Integração Pinia Store + Composable

(1) ▶Exemplo: cartStore Aprimorado

TYPESCRIPT
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([])
  const isLoading = ref(false)

  const totalItems = computed(() => items.value.reduce((s, i) => s + i.quantity, 0))
  const totalPrice = computed(() => items.value.reduce((s, i) => s + i.price * i.quantity, 0))
  const formattedTotal = computed(() =>
    new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(totalPrice.value)
  )

  async function fetchCart() {
    isLoading.value = true
    try {
      const cart = await $fetch('/api/cart')
      items.value = cart.items || []
    } finally {
      isLoading.value = false
    }
  }

  async function addItem(productId: number, quantity = 1) {
    const cart = await $fetch('/api/cart/add', {
      method: 'POST', body: { productId, quantity }
    })
    items.value = cart.items
  }

  async function removeItem(productId: number) {
    const cart = await $fetch('/api/cart/remove', {
      method: 'DELETE', body: { productId }
    })
    items.value = cart.items
  }

  function clearCart() {
    items.value = []
  }

  return { items, isLoading, totalItems, totalPrice, formattedTotal, fetchCart, addItem, removeItem, clearCart }
})

interface CartItem { productId: number; name: string; price: number; quantity: number; image: string }

Saída:

TEXT
// Execução Bem-sucedida

(2) ▶Exemplo: Composable useCart

TYPESCRIPT
// composables/useCart.ts
export function useCart() {
  const cartStore = useCartStore()
  const { $logger } = useNuxtApp()
  const { notify } = useNotification()

  async function addToCart(productId: number, productName: string) {
    try {
      await cartStore.addItem(productId)
      notify(`${productName} added to cart`, 'success')
      $logger.info('Item added to cart', { productId })
    } catch (err) {
      notify('Failed to add item', 'error')
      $logger.error('Add to cart failed', { productId, err })
    }
  }

  async function removeFromCart(productId: number) {
    try {
      await cartStore.removeItem(productId)
      notify('Item removed from cart', 'info')
    } catch (err) {
      notify('Failed to remove item', 'error')
    }
  }

  return {
    items: computed(() => cartStore.items),
    totalItems: computed(() => cartStore.totalItems),
    totalPrice: computed(() => cartStore.totalPrice),
    formattedTotal: computed(() => cartStore.formattedTotal),
    isLoading: computed(() => cartStore.isLoading),
    addToCart,
    removeFromCart
  }
}

Saída:

TEXT
// Execução Bem-sucedida

6. Configuração do Middleware de Permissões

(1) ▶Exemplo: Uma Arquitetura Completa de Middleware

TYPESCRIPT
// middleware/01-auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
  const publicPaths = ['/', '/products', '/about', '/login', '/register']
  if (publicPaths.some(p => to.path === p || to.path.startsWith('/products'))) return

  const token = useCookie('auth-token')
  if (!token.value) return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
})

Saída:

TEXT
// Execução Bem-sucedida
TYPESCRIPT
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
  const userCookie = useCookie('user-data')
  if (!userCookie.value) return navigateTo('/login')
  if ((userCookie.value as any).role !== 'admin') {
    throw createError({ statusCode: 403, message: 'Admin access required' })
  }
})

7. Teste de Integração Ponta a Ponta de Páginas

(1) ▶Exemplo: Página de Detalhes do Produto (Fluxo de Dados Completo)

VUE
<!-- pages/products/[id].vue -->
<template>
  <div v-if="product" class="product-detail">
    <img :src="product.image" :alt="product.name" />
    <div class="info">
      <h1>{{ product.name }}</h1>
      <p class="price">{{ formattedPrice }}</p>
      <p>{{ product.reviewCount }} reviews</p>
      <button @click="handleAddToCart" :disabled="adding">
        {{ adding ? 'Adding...' : 'Add to Cart' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
const { formatted } = usePriceFormat(computed(() => product.value?.price || 0))
const formattedPrice = formatted

const { addToCart } = useCart()
const adding = ref(false)

async function handleAddToCart() {
  if (!product.value) return
  adding.value = true
  await addToCart(product.value.id, product.value.name)
  adding.value = false
}
</script>

Saída:

TEXT
// Execução Bem-sucedida

8. Checklist de Teste de Integração

Item de Teste Método de Verificação Resultado Esperado
API Disponível curl /api/products Retorna JSON de produtos
Compartilhamento de Estado da Store Adicionar item na página inicial → Ver quantidade na página de detalhes Quantidades correspondem
Chamada de Composable Clicar em "Adicionar ao Carrinho" Sucesso: Adicionado + Notificação
Proteção de Middleware Acesso não autenticado a /admin Redirecionar para /login
Plugin em execução Ver console Saída do Logger presente
Hidratação de Dados SSR Ver Código Fonte HTML Contém Dados do Produto
Persistência de Cookies Atualizar Página Dados do Carrinho Mantidos

❓Perguntas Frequentes

P O que devo fazer se os dados da Store e da API estiverem dessincronizados?
R Após a Store chamar a API via addItem, use o valor retornado para atualizar o array items para garantir que a Store e o backend permaneçam sincronizados. Não apenas modifique a Store sem chamar a API.
P É redundante usar Store e $fetch no Composable?
R Não. A Store gerencia o estado do lado do cliente, enquanto $fetch sincroniza com o backend. useCart encapsula ambos juntos, então quem chama não precisa se preocupar com os detalhes.
P O middleware de página e o middleware de API lidam com autenticação. Isso causará conflito?
R Não. O middleware de página protege transições de rota (frontend), enquanto o middleware do servidor protege requisições de API (backend). Os dois são complementares e ambos são necessários.
P Como depurar o fluxo de dados ponta a ponta?
R Use o painel Pinia no Nuxt DevTools para ver o estado da Store, o painel Network para ver requisições de API, e o console para ver a saída do logger. Solucione passo a passo, começando pela resposta da API → atualização da Store → renderização da página.
P Como os dados do carrinho de compras são inicializados durante o SSR?
R Durante o SSR, o ID da sessão é obtido via cookie, e a API retorna o carrinho de compras com base no ID da sessão. Após a conclusão da hidratação do lado do cliente, a Store obtém os dados do payload da API.
P Quando os dados mock da Fase 2 serão substituídos pelo banco de dados real?
R Na Lição 20 da Fase 4, substituiremos os mocks pelo Prisma. O formato da interface da API já foi projetado; tudo o que resta é modificar a implementação interna da API do servidor.

📖Resumo


📝Exercícios

  1. Exercício Básico (Dificuldade: ⭐): Complete a integração da API do carrinho de compras com o cartStore para que a contagem do carrinho seja atualizada quando o botão "Adicionar ao Carrinho" for clicado na página de detalhes do produto.
  2. Exercício Avançado (Dificuldade: ⭐⭐): Implemente um sistema completo de middleware: Se um usuário não autenticado acessar /checkout, redirecione para a página de login; se um usuário comum acessar /admin, retorne um erro 403.
  3. Desafio (Dificuldade: ⭐⭐⭐): Implemente um fluxo de compra ponta a ponta: Navegar produtos → Adicionar ao carrinho → Ver carrinho → Checkout (simular pagamento usando o plugin Stripe), garantindo consistência de dados durante todo o processo.

---|

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%