📋 Informações Gerais
Base URL
https://api.cwgravatas.com/api
Autenticação
- A API usa Laravel Sanctum para autenticação
- Rotas protegidas requerem token no header:
Authorization: Bearer {token} - Rotas de admin requerem role
admin
🔐 Autenticação
POST
/api/login
Público
Realiza login e retorna token de acesso
📄 Ver Payload e Response
📤 Request Body
{
"email": "admin@example.com",
"password": "senha123"
}
📥 Response 200 - Success
{
"message": "Login realizado com sucesso",
"user": {
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "admin"
},
"token": "1|abc123def456..."
}
📥 Response 401 - Unauthorized
{
"message": "Credenciais inválidas"
}
POST
/api/logout
Protegido
Revoga o token atual
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"message": "Logout realizado com sucesso"
}
GET
/api/me
Protegido
Retorna dados do usuário autenticado
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "admin",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
}
🏠 Home
GET
/api/home
Público
Retorna todos os dados necessários para a página inicial (Instagram Links, Tipos de Produtos e Carrossel)
📄 Ver Response
📋 Query Parameters (Opcionais)
screen_size: mobile | tablet | desktop - Filtra as imagens do carrossel por tamanho de tela - Exemplo: /api/home?screen_size=mobile
📥 Response 200 - Success
{
"success": true,
"message": "Dados da home recuperados com sucesso",
"data": {
"instagram_links": [
{
"id": 1,
"label": "Instagram Oficial",
"link": "https://instagram.com/cwgravatas",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
},
{
"id": 2,
"label": "Promoções",
"link": "https://instagram.com/cwgravatas/promocoes",
"created_at": "2026-01-30T11:00:00.000000Z",
"updated_at": "2026-01-30T11:00:00.000000Z"
}
],
"product_types": [
{
"id": 1,
"label": "Gravata",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
},
{
"id": 2,
"label": "Suspensório",
"created_at": "2026-01-30T10:05:00.000000Z",
"updated_at": "2026-01-30T10:05:00.000000Z"
}
],
"carousel_images": [
{
"id": 1,
"screen_size": "desktop",
"cloudinary_public_id": "home-carousel/abc123",
"url": "https://res.cloudinary.com/.../home-carousel/abc123.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../home-carousel/abc123_thumb.jpg",
"ordem": 1,
"ativa": true,
"created_at": "2026-01-30T12:00:00.000000Z",
"updated_at": "2026-01-30T12:00:00.000000Z"
},
{
"id": 2,
"screen_size": "desktop",
"cloudinary_public_id": "home-carousel/def456",
"url": "https://res.cloudinary.com/.../home-carousel/def456.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../home-carousel/def456_thumb.jpg",
"ordem": 2,
"ativa": true,
"created_at": "2026-01-30T12:05:00.000000Z",
"updated_at": "2026-01-30T12:05:00.000000Z"
}
]
}
}
💡 Observações
• Resposta cacheada por 30 minutos • Retorna apenas imagens ativas do carrossel • Tipos de produtos são ordenados por label • Rate limit: 60 requisições por minuto
🛍️ Produtos
Rotas Públicas
GET
/api/produtos
Público
Lista produtos ativos (para o site público)
📄 Ver Response
📥 Response 200 - Success
[
{
"id": 1,
"imagem": "https://res.cloudinary.com/.../gravata1.jpg",
"imagens": [
{
"id": 1,
"url": "https://res.cloudinary.com/.../gravata1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_gravata1.jpg",
"ordem": 0,
"largura": 800,
"altura": 600,
"tamanho_bytes": 245678
}
],
"nome": "Gravata Slim Azul Marinho",
"preco": 89.90,
"descricao": "Gravata slim em poliéster de alta qualidade",
"tamanho": "Slim",
"tecido": "Poliéster",
"cor": "Azul Marinho",
"estampa": "Lisa",
"textura": "Acetinada",
"largura": "5cm",
"comprimento": "145cm",
"observacao": null,
"status": true,
"quantidade": 50
}
]
GET
/api/produtos/{id}
Público
Mostra um produto específico
📄 Ver Response
📥 Response 200 - Success
{
"id": 1,
"imagem": "https://res.cloudinary.com/.../gravata1.jpg",
"imagens": [
{
"id": 1,
"url": "https://res.cloudinary.com/.../gravata1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_gravata1.jpg",
"ordem": 0,
"largura": 800,
"altura": 600,
"tamanho_bytes": 245678
}
],
"nome": "Gravata Slim Azul Marinho",
"preco": 89.90,
"descricao": "Gravata slim em poliéster de alta qualidade",
"tamanho": "Slim",
"tecido": "Poliéster",
"cor": "Azul Marinho",
"estampa": "Lisa",
"textura": "Acetinada",
"largura": "5cm",
"comprimento": "145cm",
"observacao": null,
"status": true,
"quantidade": 50
}
📥 Response 404 - Not Found
{
"message": "Produto não encontrado"
}
Rotas de Administração
GET
/api/produtos/todos
Admin
Lista TODOS os produtos (incluindo inativos)
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success (Admin)
[
{
"id": 1,
"imagem": "https://res.cloudinary.com/.../gravata1.jpg",
"imagens": [...],
"nome": "Gravata Slim Azul Marinho",
"preco": 89.90,
"preco_compra": 45.00, // ⚠️ Campo visível apenas para admins
"descricao": "Gravata slim em poliéster",
"status": true,
"quantidade": 50,
...
},
{
"id": 2,
"nome": "Gravata Clássica Preta",
"preco": 79.90,
"preco_compra": 40.00,
"status": false, // ⚠️ Inativo - visível apenas nesta rota
"quantidade": 0,
...
}
]
POST
/api/produtos
Admin
Cria um novo produto
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"nome": "Gravata Slim Azul Marinho",
"preco": 89.90,
"preco_compra": 45.00,
"descricao": "Gravata slim em poliéster de alta qualidade",
"tamanho": "Slim",
"tecido": "Poliéster",
"cor": "Azul Marinho",
"estampa": "Lisa",
"textura": "Acetinada",
"largura": "5cm",
"comprimento": "145cm",
"observacao": "Produto importado",
"status": true,
"quantidade": 50,
"imagens": [ // Opcional: imagens já uploadadas
{
"cloudinary_public_id": "produtos/abc123",
"url": "https://res.cloudinary.com/.../gravata1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb.jpg",
"largura": 800,
"altura": 600,
"tamanho_bytes": 245678
}
]
}
📥 Response 201 - Created
{
"id": 1,
"imagem": "https://res.cloudinary.com/.../gravata1.jpg",
"imagens": [...],
"nome": "Gravata Slim Azul Marinho",
"preco": 89.90,
"preco_compra": 45.00,
"descricao": "Gravata slim em poliéster de alta qualidade",
"status": true,
"quantidade": 50,
...
}
PUT
/api/produtos/{id}
Admin
Atualiza um produto
📄 Ver Payload e Response
📤 Request Body (Campos opcionais)
{
"nome": "Gravata Slim Azul Marinho Atualizada",
"preco": 99.90,
"status": false,
"quantidade": 30
// Envie apenas os campos que deseja atualizar
}
📥 Response 200 - Success
{
"id": 1,
"nome": "Gravata Slim Azul Marinho Atualizada",
"preco": 99.90,
"status": false,
"quantidade": 30,
...
}
DELETE
/api/produtos/{id}
Admin
Remove um produto
📄 Ver Response
📥 Response 200 - Success
{
"message": "Produto removido com sucesso"
}
PATCH
/api/produtos/{id}/estoque
Admin
Atualiza o estoque (entrada/saída)
📄 Ver Payload e Response
📤 Request Body
{
"quantidade": 10 // Use número positivo para entrada, negativo para saída
}
📥 Response 200 - Success
{
"message": "Estoque atualizado",
"produto": {
"id": 1,
"nome": "Gravata Slim Azul Marinho",
"quantidade": 60, // estoque anterior 50 + 10
...
}
}
📥 Response 400 - Estoque Insuficiente
{
"message": "Estoque insuficiente",
"estoque_atual": 5
}
Gestão de Imagens
GET
/api/produtos/{id}/imagens
Admin
Lista imagens do produto
📄 Ver Response
📥 Response 200 - Success
{
"imagens": [
{
"id": 1,
"url": "https://res.cloudinary.com/.../gravata1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_gravata1.jpg",
"ordem": 0,
"largura": 800,
"altura": 600,
"tamanho_bytes": 245678
},
{
"id": 2,
"url": "https://res.cloudinary.com/.../gravata2.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_gravata2.jpg",
"ordem": 1,
"largura": 800,
"altura": 600,
"tamanho_bytes": 198234
}
],
"total": 2,
"limite": 5
}
POST
/api/produtos/{id}/imagens
Admin
Upload múltiplas imagens (até 5)
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: multipart/form-data
📤 Request Body (multipart/form-data)
imagens[]: [File] (jpg, jpeg, png, webp - max 2MB cada) imagens[]: [File] imagens[]: [File] // Até 5 imagens por envio
📥 Response 201 - Created
{
"message": "3 imagem(ns) enviada(s) com sucesso",
"imagens": [
{
"id": 3,
"url": "https://res.cloudinary.com/.../new1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_new1.jpg",
"ordem": 2,
"largura": 800,
"altura": 600,
"tamanho_bytes": 223456
}
],
"total_imagens": 5
}
📥 Response 422 - Limite Excedido
{
"message": "Limite de 5 imagens por produto excedido.",
"imagens_atuais": 4,
"maximo_permitido": 1
}
DELETE
/api/produtos/{id}/imagens/{imagemId}
Admin
Remove uma imagem
📄 Ver Response
📥 Response 200 - Success
{
"message": "Imagem removida com sucesso",
"total_imagens": 4
}
PUT
/api/produtos/{id}/imagens/reordenar
Admin
Reordena imagens
📄 Ver Payload e Response
📤 Request Body
{
"ordem": [3, 1, 2, 5, 4] // Array de IDs na nova ordem
}
📥 Response 200 - Success
{
"message": "Ordem das imagens atualizada",
"imagens": [
{
"id": 3,
"ordem": 0, // Primeira posição - imagem principal
...
},
{
"id": 1,
"ordem": 1,
...
}
]
}
PUT
/api/produtos/{id}/imagens/{imagemId}/principal
Admin
Define imagem principal
📄 Ver Response
📥 Response 200 - Success
{
"message": "Imagem principal definida com sucesso",
"imagem_principal": {
"id": 3,
"url": "https://res.cloudinary.com/.../gravata3.jpg",
"ordem": 0,
...
}
}
🏷️ Tipos de Produtos
Rotas Públicas
GET
/api/product-types
Público
Lista todos os tipos de produtos
📄 Ver Response
📥 Response 200 - Success
[
{
"id": 1,
"label": "Gravata",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
},
{
"id": 2,
"label": "Gravata Borboleta",
"created_at": "2026-01-30T10:05:00.000000Z",
"updated_at": "2026-01-30T10:05:00.000000Z"
},
{
"id": 3,
"label": "Suspensório",
"created_at": "2026-01-30T10:10:00.000000Z",
"updated_at": "2026-01-30T10:10:00.000000Z"
}
]
GET
/api/product-types/{id}
Público
Mostra um tipo de produto específico
📄 Ver Response
📥 Response 200 - Success
{
"id": 1,
"label": "Gravata",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
}
📥 Response 404 - Not Found
{
"message": "Tipo de produto não encontrado"
}
Rotas de Administração
POST
/api/product-types
Admin
Cria um novo tipo de produto
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"label": "Lenço de Bolso"
}
📥 Response 201 - Created
{
"id": 4,
"label": "Lenço de Bolso",
"created_at": "2026-01-30T15:30:00.000000Z",
"updated_at": "2026-01-30T15:30:00.000000Z"
}
📥 Response 422 - Validation Error
{
"message": "Erro de validação",
"errors": {
"label": [
"Este tipo de produto já existe"
]
}
}
PUT
/api/product-types/{id}
Admin
Atualiza um tipo de produto
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"label": "Gravata Tradicional"
}
📥 Response 200 - Success
{
"id": 1,
"label": "Gravata Tradicional",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T15:35:00.000000Z"
}
📥 Response 404 - Not Found
{
"message": "Tipo de produto não encontrado"
}
DELETE
/api/product-types/{id}
Admin
Remove um tipo de produto
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"message": "Tipo de produto removido com sucesso"
}
📥 Response 404 - Not Found
{
"message": "Tipo de produto não encontrado"
}
📸 Links do Instagram
Rotas Públicas
GET
/api/instagram-links
Público
Lista todos os links do Instagram
📄 Ver Response
📥 Response 200 - Success
{
"success": true,
"message": "Links do Instagram recuperados com sucesso",
"data": [
{
"id": 1,
"label": "Instagram Oficial",
"link": "https://instagram.com/cwgravatas",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
},
{
"id": 2,
"label": "Promoções",
"link": "https://instagram.com/cwgravatas/promocoes",
"created_at": "2026-01-30T11:00:00.000000Z",
"updated_at": "2026-01-30T11:00:00.000000Z"
}
]
}
Rotas de Administração
POST
/api/instagram-links
Admin
Cria um novo link do Instagram
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"label": "Instagram Stories",
"link": "https://instagram.com/stories/cwgravatas"
}
📋 Regras de Validação
label: obrigatório, string, máximo 255 caracteres link: obrigatório, URL válida, máximo 255 caracteres
📥 Response 201 - Created
{
"success": true,
"message": "Link do Instagram criado com sucesso",
"data": {
"id": 3,
"label": "Instagram Stories",
"link": "https://instagram.com/stories/cwgravatas",
"created_at": "2026-01-30T16:00:00.000000Z",
"updated_at": "2026-01-30T16:00:00.000000Z"
}
}
📥 Response 422 - Validation Error
{
"success": false,
"message": "Dados inválidos",
"data": {
"label": [
"O campo label é obrigatório"
],
"link": [
"O campo link deve ser uma URL válida"
]
}
}
DELETE
/api/instagram-links/{id}
Admin
Remove um link do Instagram
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"success": true,
"message": "Link do Instagram deletado com sucesso",
"data": null
}
📥 Response 404 - Not Found
{
"success": false,
"message": "Link do Instagram não encontrado",
"data": null
}
🎠 Carrossel do Home
Rotas Públicas
GET
/api/home-carousel
Público
Lista imagens ativas do carrossel (filtro por screen_size: ?screen_size=mobile)
📄 Ver Response
🔍 Query Parameters
screen_size: mobile | tablet | desktop (opcional)
📥 Response 200 - Success
[
{
"id": 1,
"label": "Banner Principal Desktop",
"screen_size": "desktop",
"cloudinary_public_id": "home-carousel/banner1_abc123",
"url": "https://res.cloudinary.com/.../banner1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_banner1.jpg",
"largura": 1920,
"altura": 600,
"tamanho_bytes": 456789,
"ordem": 0,
"ativo": true,
"link": "https://example.com/produtos/destaque",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
},
{
"id": 2,
"label": "Promoção Mobile",
"screen_size": "mobile",
"url": "https://res.cloudinary.com/.../promo_mobile.jpg",
"ordem": 1,
"ativo": true,
"link": null,
...
}
]
GET
/api/home-carousel/{id}
Público
Mostra uma imagem específica
📄 Ver Response
📥 Response 200 - Success
{
"id": 1,
"label": "Banner Principal Desktop",
"screen_size": "desktop",
"cloudinary_public_id": "home-carousel/banner1_abc123",
"url": "https://res.cloudinary.com/.../banner1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_banner1.jpg",
"largura": 1920,
"altura": 600,
"tamanho_bytes": 456789,
"ordem": 0,
"ativo": true,
"link": "https://example.com/produtos/destaque",
"created_at": "2026-01-30T10:00:00.000000Z",
"updated_at": "2026-01-30T10:00:00.000000Z"
}
📥 Response 404 - Not Found
{
"message": "Imagem não encontrada"
}
Rotas de Administração
GET
/api/home-carousel/all
Admin
Lista TODAS as imagens (incluindo inativas)
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
[
{
"id": 1,
"label": "Banner Principal Desktop",
"screen_size": "desktop",
"url": "https://res.cloudinary.com/.../banner1.jpg",
"ordem": 0,
"ativo": true,
...
},
{
"id": 2,
"label": "Banner Antigo",
"screen_size": "mobile",
"url": "https://res.cloudinary.com/.../old_banner.jpg",
"ordem": 5,
"ativo": false, // ⚠️ Inativo - visível apenas nesta rota
...
}
]
POST
/api/home-carousel
Admin
Cria nova imagem com upload no Cloudinary
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: multipart/form-data
📤 Request Body (multipart/form-data)
label: "Banner Principal Desktop" (obrigatório) screen_size: "desktop" | "mobile" | "tablet" (obrigatório) imagem: [File] (jpg, jpeg, png, webp - max 5MB) (obrigatório) ordem: 0 (opcional, padrão: 0) ativo: true (opcional, padrão: true) link: "https://example.com/destaque" (opcional)
📥 Response 201 - Created
{
"id": 5,
"label": "Banner Principal Desktop",
"screen_size": "desktop",
"cloudinary_public_id": "home-carousel/new_banner_xyz789",
"url": "https://res.cloudinary.com/.../new_banner.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_new_banner.jpg",
"largura": 1920,
"altura": 600,
"tamanho_bytes": 512345,
"ordem": 0,
"ativo": true,
"link": "https://example.com/destaque",
"created_at": "2026-01-30T16:00:00.000000Z",
"updated_at": "2026-01-30T16:00:00.000000Z"
}
📥 Response 422 - Validation Error
{
"message": "Erro de validação",
"errors": {
"screen_size": [
"O screen_size deve ser: mobile, tablet ou desktop"
],
"imagem": [
"A imagem não pode ter mais de 5MB"
]
}
}
PUT
/api/home-carousel/{id}
Admin
Atualiza imagem (pode substituir arquivo)
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: multipart/form-data
📤 Request Body (todos campos opcionais)
label: "Banner Atualizado" screen_size: "mobile" imagem: [File] // Se enviado, substitui a imagem no Cloudinary ordem: 1 ativo: false link: "https://example.com/nova-pagina"
📥 Response 200 - Success
{
"id": 1,
"label": "Banner Atualizado",
"screen_size": "mobile",
"cloudinary_public_id": "home-carousel/updated_xyz123",
"url": "https://res.cloudinary.com/.../updated_banner.jpg",
"ordem": 1,
"ativo": false,
"link": "https://example.com/nova-pagina",
...
}
DELETE
/api/home-carousel/{id}
Admin
Remove imagem (e do Cloudinary)
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"message": "Imagem removida com sucesso"
}
📥 Response 404 - Not Found
{
"message": "Imagem não encontrada"
}
PUT
/api/home-carousel/reorder
Admin
Reordena imagens
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"images": [
{ "id": 3, "ordem": 0 },
{ "id": 1, "ordem": 1 },
{ "id": 2, "ordem": 2 }
]
}
📥 Response 200 - Success
{
"message": "Imagens reordenadas com sucesso"
}
PATCH
/api/home-carousel/{id}/toggle
Admin
Ativa/desativa imagem
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
{
"message": "Status atualizado com sucesso",
"ativo": false // Novo status
}
📤 Upload (Cloudinary)
POST
/api/upload/imagens
Admin
Upload de imagens para o Cloudinary (sem associar a produto)
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: multipart/form-data
📤 Request Body (multipart/form-data)
imagens[]: [File] (jpg, jpeg, png, webp - max 2MB cada) imagens[]: [File] imagens[]: [File] // De 1 a 5 imagens por envio
📥 Response 201 - Created
{
"message": "3 imagem(ns) enviada(s) com sucesso",
"imagens": [
{
"cloudinary_public_id": "produtos/abc123def456",
"url": "https://res.cloudinary.com/.../imagem1.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_imagem1.jpg",
"largura": 800,
"altura": 600,
"tamanho_bytes": 245678,
"ordem": 0
},
{
"cloudinary_public_id": "produtos/xyz789ghi012",
"url": "https://res.cloudinary.com/.../imagem2.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_imagem2.jpg",
"largura": 800,
"altura": 600,
"tamanho_bytes": 198234,
"ordem": 1
},
{
"cloudinary_public_id": "produtos/mno345pqr678",
"url": "https://res.cloudinary.com/.../imagem3.jpg",
"url_thumbnail": "https://res.cloudinary.com/.../thumb_imagem3.jpg",
"largura": 800,
"altura": 600,
"tamanho_bytes": 312456,
"ordem": 2
}
]
}
// ℹ️ Use esses dados no campo "imagens" ao criar um produto
📥 Response 422 - Validation Error
{
"message": "The given data was invalid.",
"errors": {
"imagens": [
"Máximo de 5 imagens por envio."
],
"imagens.0": [
"Cada imagem não pode ter mais de 2MB."
]
}
}
DELETE
/api/upload/imagem
Admin
Remove imagem do Cloudinary (cancelar upload)
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"cloudinary_public_id": "produtos/abc123def456"
}
📥 Response 200 - Success
{
"message": "Imagem removida com sucesso"
}
📥 Response 500 - Error
{
"message": "Erro ao remover imagem"
}
👥 Usuários
GET
/api/users
Admin
Lista todos os usuários
📄 Ver Response
🔑 Headers Requeridos
Authorization: Bearer {token}
📥 Response 200 - Success
[
{
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "admin",
"created_at": "2026-01-25T10:00:00.000000Z",
"updated_at": "2026-01-25T10:00:00.000000Z"
},
{
"id": 2,
"name": "João Silva",
"email": "joao@example.com",
"role": "user",
"created_at": "2026-01-28T14:30:00.000000Z",
"updated_at": "2026-01-28T14:30:00.000000Z"
}
]
GET
/api/users/{id}
Admin
Mostra um usuário específico
📄 Ver Response
📥 Response 200 - Success
{
"id": 1,
"name": "Admin User",
"email": "admin@example.com",
"role": "admin",
"created_at": "2026-01-25T10:00:00.000000Z",
"updated_at": "2026-01-25T10:00:00.000000Z"
}
📥 Response 404 - Not Found
{
"message": "Usuário não encontrado"
}
POST
/api/users
Admin
Cria um novo usuário
📄 Ver Payload e Response
🔑 Headers Requeridos
Authorization: Bearer {token}
Content-Type: application/json
📤 Request Body
{
"name": "Maria Santos",
"email": "maria@example.com",
"password": "Senha@123"
}
// ⚠️ Regras de senha:
// - Mínimo 8 caracteres
// - Pelo menos 1 letra maiúscula
// - Pelo menos 1 número
// - Pelo menos 1 símbolo especial (@$!%*#?&)
📥 Response 201 - Created
{
"id": 3,
"name": "Maria Santos",
"email": "maria@example.com",
"role": "user",
"created_at": "2026-01-30T16:45:00.000000Z",
"updated_at": "2026-01-30T16:45:00.000000Z"
}
📥 Response 422 - Validation Error
{
"message": "The given data was invalid.",
"errors": {
"email": [
"The email has already been taken."
],
"password": [
"A senha deve conter pelo menos uma letra maiúscula, um número e um símbolo especial (@$!%*#?&)."
]
}
}
PUT
/api/users/{id}
Admin
Atualiza um usuário
📄 Ver Payload e Response
📤 Request Body (campos opcionais)
{
"name": "Maria Santos Silva",
"email": "maria.silva@example.com",
"password": "NovaSenha@456"
// Envie apenas os campos que deseja atualizar
}
📥 Response 200 - Success
{
"id": 3,
"name": "Maria Santos Silva",
"email": "maria.silva@example.com",
"role": "user",
"created_at": "2026-01-30T16:45:00.000000Z",
"updated_at": "2026-01-30T17:00:00.000000Z"
}
DELETE
/api/users/{id}
Admin
Remove um usuário
📄 Ver Response
📥 Response 200 - Success
{
"message": "Usuário removido com sucesso"
}
📥 Response 404 - Not Found
{
"message": "Usuário não encontrado"
}
💻 TypeScript Interfaces para Frontend
📦 Interfaces TypeScript
Use estas interfaces no seu projeto frontend para tipagem forte:
💙 TypeScript Types
// ========== AUTENTICAÇÃO ==========
interface LoginRequest {
email: string;
password: string;
}
interface LoginResponse {
message: string;
user: User;
token: string;
}
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
created_at?: string;
updated_at?: string;
}
// ========== PRODUTOS ==========
interface Produto {
id: number;
imagem: string;
imagens: ProdutoImagem[];
nome: string;
preco: number;
preco_compra?: number; // Apenas para admins
descricao: string | null;
tamanho: string | null;
tecido: string | null;
cor: string | null;
estampa: string | null;
textura: string | null;
largura: string | null;
comprimento: string | null;
observacao: string | null;
status: boolean;
quantidade: number;
}
interface ProdutoImagem {
id: number;
url: string;
url_thumbnail: string;
ordem: number;
largura: number;
altura: number;
tamanho_bytes: number;
}
interface CreateProdutoRequest {
nome: string;
preco: number;
preco_compra?: number;
descricao?: string;
tamanho?: string;
tecido?: string;
cor?: string;
estampa?: string;
textura?: string;
largura?: string;
comprimento?: string;
observacao?: string;
status?: boolean;
quantidade: number;
imagens?: ImagemUpload[];
}
interface ImagemUpload {
cloudinary_public_id: string;
url: string;
url_thumbnail?: string;
largura?: number;
altura?: number;
tamanho_bytes?: number;
}
// ========== TIPOS DE PRODUTOS ==========
interface ProductType {
id: number;
label: string;
created_at: string;
updated_at: string;
}
// ========== CARROSSEL ==========
interface HomeCarouselImage {
id: number;
label: string;
screen_size: "mobile" | "tablet" | "desktop";
cloudinary_public_id: string;
url: string;
url_thumbnail: string;
largura: number;
altura: number;
tamanho_bytes: number;
ordem: number;
ativo: boolean;
link: string | null;
created_at: string;
updated_at: string;
}
// ========== RESPONSES DE ERRO ==========
interface ErrorResponse {
message: string;
errors?: Record<string, string[]>;
}
⚛️ Exemplo de Serviço API (Axios)
import axios, { AxiosInstance } from 'axios';
class ApiService {
private api: AxiosInstance;
constructor() {
this.api = axios.create({
baseURL: 'http://localhost:8000/api',
headers: { 'Content-Type': 'application/json' },
});
// Adiciona token automaticamente
this.api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
}
// Autenticação
async login(data: LoginRequest): Promise<LoginResponse> {
const response = await this.api.post('/login', data);
return response.data;
}
// Produtos
async getProdutos(): Promise<Produto[]> {
const response = await this.api.get('/produtos');
return response.data;
}
async createProduto(data: CreateProdutoRequest): Promise<Produto> {
const response = await this.api.post('/produtos', data);
return response.data;
}
// Upload de Imagens
async uploadImagens(files: File[]): Promise<ImagemUpload[]> {
const formData = new FormData();
files.forEach(file => formData.append('imagens[]', file));
const response = await this.api.post('/upload/imagens', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});
return response.data.imagens;
}
}
export default new ApiService();
📦 Estrutura de Respostas Padronizadas
Todas as respostas da API seguem um padrão consistente com campos success, message e data:
Resposta de Sucesso (200/201)
{
"success": true,
"message": "Operação realizada com sucesso",
"data": {
"id": 1,
"nome": "Gravata Azul",
"preco": "49.90"
}
}
Resposta de Erro (400/500)
{
"success": false,
"message": "Descrição do erro"
}
Erro de Validação (422)
{
"success": false,
"message": "Os dados fornecidos são inválidos",
"errors": {
"nome": ["O campo nome é obrigatório."],
"preco": ["O preço deve ser maior que zero."]
}
}
Recurso Não Encontrado (404)
{
"success": false,
"message": "Produto não encontrado"
}
Não Autorizado (401)
{
"success": false,
"message": "Não autenticado"
}
Sem Permissão (403)
{
"success": false,
"message": "Acesso negado. Você não tem permissão para esta ação."
}
Resposta Paginada
{
"success": true,
"message": "Produtos listados com sucesso",
"data": {
"current_page": 1,
"data": [
{ "id": 1, "nome": "Produto 1" },
{ "id": 2, "nome": "Produto 2" }
],
"first_page_url": "http://api.cwgravatas.com/api/produtos?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "http://api.cwgravatas.com/api/produtos?page=5",
"next_page_url": "http://api.cwgravatas.com/api/produtos?page=2",
"path": "http://api.cwgravatas.com/api/produtos",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 73
}
}
⚙️ Convenções e Boas Práticas
- ✅ Type Hints: Todos os controllers usam type hints para parâmetros e return types
- ✅ Form Requests: Validações centralizadas em classes dedicadas
- ✅ Service Layer: Lógica de negócio encapsulada em Services
- ✅ Traits Reutilizáveis: ApiResponse e FindsOrFails eliminam duplicação
- ✅ Model Events: Sincronização automática de dados relacionados
- ✅ Paginação: Listas retornam dados paginados (15-20 itens por página)
- ✅ Cache Estratégico: Recursos estáticos cacheados por 1 hora
- ✅ Mensagens Customizadas: Erros de validação em português