From aaa4955fd938ecb0e15a39008721894194c87ee9 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Fri, 19 Dec 2025 18:09:25 -0300 Subject: [PATCH] Add full CRUD coverage to core API routes --- backend/docs/docs.go | 999 ++++++++++++++++++ backend/docs/swagger.json | 999 ++++++++++++++++++ backend/docs/swagger.yaml | 627 +++++++++++ backend/internal/http/handler/handler.go | 359 +++++++ .../internal/repository/postgres/postgres.go | 202 ++++ backend/internal/server/server.go | 8 + backend/internal/usecase/usecase.go | 33 + 7 files changed, 3227 insertions(+) diff --git a/backend/docs/docs.go b/backend/docs/docs.go index f398f63..d0bed9e 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -72,7 +72,163 @@ const docTemplate = `{ } } }, + "/api/companies/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "tags": [ + "Empresas" + ], + "summary": "Remover empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Atualizar empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Campos para atualização", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.updateCompanyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/orders": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Pedidos" + ], + "summary": "Listar pedidos", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Order" + } + } + } + } + }, "post": { "consumes": [ "application/json" @@ -107,6 +263,11 @@ const docTemplate = `{ }, "/api/orders/{id}": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -131,10 +292,58 @@ const docTemplate = `{ } } } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Pedidos" + ], + "summary": "Remover pedido", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } } }, "/api/orders/{id}/payment": { "post": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -163,6 +372,11 @@ const docTemplate = `{ }, "/api/orders/{id}/status": { "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "consumes": [ "application/json" ], @@ -251,6 +465,137 @@ const docTemplate = `{ } } }, + "/api/products/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Produtos" + ], + "summary": "Obter produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Product" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "tags": [ + "Produtos" + ], + "summary": "Remover produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Produtos" + ], + "summary": "Atualizar produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Campos para atualização", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.updateProductRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Product" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/auth/login": { "post": { "description": "Autentica usuário e retorna token JWT.", @@ -355,6 +700,334 @@ const docTemplate = `{ } } }, + "/api/v1/cart": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Carrinho" + ], + "summary": "Obter carrinho", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Carrinho" + ], + "summary": "Adicionar item ao carrinho", + "parameters": [ + { + "description": "Item do carrinho", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.addCartItemRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/cart/{id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Carrinho" + ], + "summary": "Remover item do carrinho", + "parameters": [ + { + "type": "string", + "description": "Cart item ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/companies/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter minha empresa", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + } + } + } + }, + "/api/v1/companies/{id}/rating": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter avaliação da empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CompanyRating" + } + } + } + } + }, + "/api/v1/companies/{id}/verify": { + "patch": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Empresas" + ], + "summary": "Verificar empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + } + } + } + }, + "/api/v1/dashboard/admin": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Dashboard do administrador", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.AdminDashboard" + } + } + } + } + }, + "/api/v1/dashboard/seller": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Dashboard do vendedor", + "parameters": [ + { + "type": "string", + "description": "Seller ID", + "name": "seller_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.SellerDashboard" + } + } + } + } + }, + "/api/v1/inventory": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Estoque" + ], + "summary": "Listar estoque", + "parameters": [ + { + "type": "integer", + "description": "Dias para expiração", + "name": "expires_in_days", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.InventoryItem" + } + } + } + } + } + }, + "/api/v1/inventory/adjust": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Estoque" + ], + "summary": "Ajustar estoque", + "parameters": [ + { + "description": "Ajuste de estoque", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.inventoryAdjustRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.InventoryItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/payments/webhook": { "post": { "consumes": [ @@ -388,8 +1061,60 @@ const docTemplate = `{ } } }, + "/api/v1/reviews": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Avaliações" + ], + "summary": "Criar avaliação", + "parameters": [ + { + "description": "Dados da avaliação", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.createReviewRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/domain.Review" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/shipments": { "post": { + "security": [ + { + "BearerAuth": [] + } + ], "consumes": [ "application/json" ], @@ -423,6 +1148,11 @@ const docTemplate = `{ }, "/api/v1/shipments/{order_id}": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -779,6 +1509,78 @@ const docTemplate = `{ } }, "definitions": { + "domain.AdminDashboard": { + "type": "object", + "properties": { + "gmv_cents": { + "type": "integer" + }, + "new_companies": { + "type": "integer" + }, + "window_start_at": { + "type": "string" + } + } + }, + "domain.CartItem": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "buyer_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "product_id": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "unit_cents": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "domain.CartSummary": { + "type": "object", + "properties": { + "discount_cents": { + "type": "integer" + }, + "discount_reason": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.CartItem" + } + }, + "subtotal_cents": { + "type": "integer" + }, + "total_cents": { + "type": "integer" + } + } + }, "domain.Company": { "type": "object", "properties": { @@ -809,6 +1611,49 @@ const docTemplate = `{ } } }, + "domain.CompanyRating": { + "type": "object", + "properties": { + "average_score": { + "type": "number" + }, + "company_id": { + "type": "string" + }, + "total_reviews": { + "type": "integer" + } + } + }, + "domain.InventoryItem": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price_cents": { + "type": "integer" + }, + "product_id": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "domain.Order": { "type": "object", "properties": { @@ -989,6 +1834,58 @@ const docTemplate = `{ } } }, + "domain.Review": { + "type": "object", + "properties": { + "buyer_id": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "order_id": { + "type": "string" + }, + "rating": { + "type": "integer" + }, + "seller_id": { + "type": "string" + } + } + }, + "domain.SellerDashboard": { + "type": "object", + "properties": { + "low_stock_alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Product" + } + }, + "orders_count": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "top_products": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.TopProduct" + } + }, + "total_sales_cents": { + "type": "integer" + } + } + }, "domain.Shipment": { "type": "object", "properties": { @@ -1050,6 +1947,23 @@ const docTemplate = `{ } } }, + "domain.TopProduct": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "product_id": { + "type": "string" + }, + "revenue_cents": { + "type": "integer" + }, + "total_quantity": { + "type": "integer" + } + } + }, "domain.User": { "type": "object", "properties": { @@ -1096,6 +2010,17 @@ const docTemplate = `{ } } }, + "handler.addCartItemRequest": { + "type": "object", + "properties": { + "product_id": { + "type": "string" + }, + "quantity": { + "type": "integer" + } + } + }, "handler.authResponse": { "type": "object", "properties": { @@ -1127,6 +2052,20 @@ const docTemplate = `{ } } }, + "handler.createReviewRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "order_id": { + "type": "string" + }, + "rating": { + "type": "integer" + } + } + }, "handler.createShipmentRequest": { "type": "object", "properties": { @@ -1164,6 +2103,20 @@ const docTemplate = `{ } } }, + "handler.inventoryAdjustRequest": { + "type": "object", + "properties": { + "delta": { + "type": "integer" + }, + "product_id": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, "handler.loginRequest": { "type": "object", "properties": { @@ -1261,6 +2214,52 @@ const docTemplate = `{ } } }, + "handler.updateCompanyRequest": { + "type": "object", + "properties": { + "cnpj": { + "type": "string" + }, + "corporate_name": { + "type": "string" + }, + "is_verified": { + "type": "boolean" + }, + "license_number": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "handler.updateProductRequest": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price_cents": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "stock": { + "type": "integer" + } + } + }, "handler.updateStatusRequest": { "type": "object", "properties": { diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index d7ba94a..e330529 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -68,7 +68,163 @@ } } }, + "/api/companies/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "tags": [ + "Empresas" + ], + "summary": "Remover empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Atualizar empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Campos para atualização", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.updateCompanyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/orders": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Pedidos" + ], + "summary": "Listar pedidos", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Order" + } + } + } + } + }, "post": { "consumes": [ "application/json" @@ -103,6 +259,11 @@ }, "/api/orders/{id}": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -127,10 +288,58 @@ } } } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Pedidos" + ], + "summary": "Remover pedido", + "parameters": [ + { + "type": "string", + "description": "Order ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } } }, "/api/orders/{id}/payment": { "post": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -159,6 +368,11 @@ }, "/api/orders/{id}/status": { "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "consumes": [ "application/json" ], @@ -247,6 +461,137 @@ } } }, + "/api/products/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Produtos" + ], + "summary": "Obter produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Product" + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "tags": [ + "Produtos" + ], + "summary": "Remover produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Produtos" + ], + "summary": "Atualizar produto", + "parameters": [ + { + "type": "string", + "description": "Product ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Campos para atualização", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.updateProductRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Product" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/auth/login": { "post": { "description": "Autentica usuário e retorna token JWT.", @@ -351,6 +696,334 @@ } } }, + "/api/v1/cart": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Carrinho" + ], + "summary": "Obter carrinho", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Carrinho" + ], + "summary": "Adicionar item ao carrinho", + "parameters": [ + { + "description": "Item do carrinho", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.addCartItemRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/cart/{id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Carrinho" + ], + "summary": "Remover item do carrinho", + "parameters": [ + { + "type": "string", + "description": "Cart item ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CartSummary" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/companies/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter minha empresa", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + } + } + } + }, + "/api/v1/companies/{id}/rating": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Empresas" + ], + "summary": "Obter avaliação da empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.CompanyRating" + } + } + } + } + }, + "/api/v1/companies/{id}/verify": { + "patch": { + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + "Empresas" + ], + "summary": "Verificar empresa", + "parameters": [ + { + "type": "string", + "description": "Company ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.Company" + } + } + } + } + }, + "/api/v1/dashboard/admin": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Dashboard do administrador", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.AdminDashboard" + } + } + } + } + }, + "/api/v1/dashboard/seller": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Dashboard" + ], + "summary": "Dashboard do vendedor", + "parameters": [ + { + "type": "string", + "description": "Seller ID", + "name": "seller_id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.SellerDashboard" + } + } + } + } + }, + "/api/v1/inventory": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Estoque" + ], + "summary": "Listar estoque", + "parameters": [ + { + "type": "integer", + "description": "Dias para expiração", + "name": "expires_in_days", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.InventoryItem" + } + } + } + } + } + }, + "/api/v1/inventory/adjust": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Estoque" + ], + "summary": "Ajustar estoque", + "parameters": [ + { + "description": "Ajuste de estoque", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.inventoryAdjustRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/domain.InventoryItem" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/payments/webhook": { "post": { "consumes": [ @@ -384,8 +1057,60 @@ } } }, + "/api/v1/reviews": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Avaliações" + ], + "summary": "Criar avaliação", + "parameters": [ + { + "description": "Dados da avaliação", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/handler.createReviewRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/domain.Review" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, "/api/v1/shipments": { "post": { + "security": [ + { + "BearerAuth": [] + } + ], "consumes": [ "application/json" ], @@ -419,6 +1144,11 @@ }, "/api/v1/shipments/{order_id}": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "produces": [ "application/json" ], @@ -775,6 +1505,78 @@ } }, "definitions": { + "domain.AdminDashboard": { + "type": "object", + "properties": { + "gmv_cents": { + "type": "integer" + }, + "new_companies": { + "type": "integer" + }, + "window_start_at": { + "type": "string" + } + } + }, + "domain.CartItem": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "buyer_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "product_id": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "unit_cents": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "domain.CartSummary": { + "type": "object", + "properties": { + "discount_cents": { + "type": "integer" + }, + "discount_reason": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.CartItem" + } + }, + "subtotal_cents": { + "type": "integer" + }, + "total_cents": { + "type": "integer" + } + } + }, "domain.Company": { "type": "object", "properties": { @@ -805,6 +1607,49 @@ } } }, + "domain.CompanyRating": { + "type": "object", + "properties": { + "average_score": { + "type": "number" + }, + "company_id": { + "type": "string" + }, + "total_reviews": { + "type": "integer" + } + } + }, + "domain.InventoryItem": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price_cents": { + "type": "integer" + }, + "product_id": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, "domain.Order": { "type": "object", "properties": { @@ -985,6 +1830,58 @@ } } }, + "domain.Review": { + "type": "object", + "properties": { + "buyer_id": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "order_id": { + "type": "string" + }, + "rating": { + "type": "integer" + }, + "seller_id": { + "type": "string" + } + } + }, + "domain.SellerDashboard": { + "type": "object", + "properties": { + "low_stock_alerts": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.Product" + } + }, + "orders_count": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "top_products": { + "type": "array", + "items": { + "$ref": "#/definitions/domain.TopProduct" + } + }, + "total_sales_cents": { + "type": "integer" + } + } + }, "domain.Shipment": { "type": "object", "properties": { @@ -1046,6 +1943,23 @@ } } }, + "domain.TopProduct": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "product_id": { + "type": "string" + }, + "revenue_cents": { + "type": "integer" + }, + "total_quantity": { + "type": "integer" + } + } + }, "domain.User": { "type": "object", "properties": { @@ -1092,6 +2006,17 @@ } } }, + "handler.addCartItemRequest": { + "type": "object", + "properties": { + "product_id": { + "type": "string" + }, + "quantity": { + "type": "integer" + } + } + }, "handler.authResponse": { "type": "object", "properties": { @@ -1123,6 +2048,20 @@ } } }, + "handler.createReviewRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string" + }, + "order_id": { + "type": "string" + }, + "rating": { + "type": "integer" + } + } + }, "handler.createShipmentRequest": { "type": "object", "properties": { @@ -1160,6 +2099,20 @@ } } }, + "handler.inventoryAdjustRequest": { + "type": "object", + "properties": { + "delta": { + "type": "integer" + }, + "product_id": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, "handler.loginRequest": { "type": "object", "properties": { @@ -1257,6 +2210,52 @@ } } }, + "handler.updateCompanyRequest": { + "type": "object", + "properties": { + "cnpj": { + "type": "string" + }, + "corporate_name": { + "type": "string" + }, + "is_verified": { + "type": "boolean" + }, + "license_number": { + "type": "string" + }, + "role": { + "type": "string" + } + } + }, + "handler.updateProductRequest": { + "type": "object", + "properties": { + "batch": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expires_at": { + "type": "string" + }, + "name": { + "type": "string" + }, + "price_cents": { + "type": "integer" + }, + "seller_id": { + "type": "string" + }, + "stock": { + "type": "integer" + } + } + }, "handler.updateStatusRequest": { "type": "object", "properties": { diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index a58e6b4..2f4ce72 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -1,5 +1,52 @@ basePath: / definitions: + domain.AdminDashboard: + properties: + gmv_cents: + type: integer + new_companies: + type: integer + window_start_at: + type: string + type: object + domain.CartItem: + properties: + batch: + type: string + buyer_id: + type: string + created_at: + type: string + expires_at: + type: string + id: + type: string + product_id: + type: string + product_name: + type: string + quantity: + type: integer + unit_cents: + type: integer + updated_at: + type: string + type: object + domain.CartSummary: + properties: + discount_cents: + type: integer + discount_reason: + type: string + items: + items: + $ref: '#/definitions/domain.CartItem' + type: array + subtotal_cents: + type: integer + total_cents: + type: integer + type: object domain.Company: properties: cnpj: @@ -20,6 +67,34 @@ definitions: updated_at: type: string type: object + domain.CompanyRating: + properties: + average_score: + type: number + company_id: + type: string + total_reviews: + type: integer + type: object + domain.InventoryItem: + properties: + batch: + type: string + expires_at: + type: string + name: + type: string + price_cents: + type: integer + product_id: + type: string + quantity: + type: integer + seller_id: + type: string + updated_at: + type: string + type: object domain.Order: properties: buyer_id: @@ -140,6 +215,40 @@ definitions: updated_at: type: string type: object + domain.Review: + properties: + buyer_id: + type: string + comment: + type: string + created_at: + type: string + id: + type: string + order_id: + type: string + rating: + type: integer + seller_id: + type: string + type: object + domain.SellerDashboard: + properties: + low_stock_alerts: + items: + $ref: '#/definitions/domain.Product' + type: array + orders_count: + type: integer + seller_id: + type: string + top_products: + items: + $ref: '#/definitions/domain.TopProduct' + type: array + total_sales_cents: + type: integer + type: object domain.Shipment: properties: carrier: @@ -180,6 +289,17 @@ definitions: zip_code: type: string type: object + domain.TopProduct: + properties: + name: + type: string + product_id: + type: string + revenue_cents: + type: integer + total_quantity: + type: integer + type: object domain.User: properties: company_id: @@ -210,6 +330,13 @@ definitions: $ref: '#/definitions/domain.User' type: array type: object + handler.addCartItemRequest: + properties: + product_id: + type: string + quantity: + type: integer + type: object handler.authResponse: properties: expires_at: @@ -230,6 +357,15 @@ definitions: shipping: $ref: '#/definitions/domain.ShippingAddress' type: object + handler.createReviewRequest: + properties: + comment: + type: string + order_id: + type: string + rating: + type: integer + type: object handler.createShipmentRequest: properties: carrier: @@ -254,6 +390,15 @@ definitions: role: type: string type: object + handler.inventoryAdjustRequest: + properties: + delta: + type: integer + product_id: + type: string + reason: + type: string + type: object handler.loginRequest: properties: email: @@ -317,6 +462,36 @@ definitions: stock: type: integer type: object + handler.updateCompanyRequest: + properties: + cnpj: + type: string + corporate_name: + type: string + is_verified: + type: boolean + license_number: + type: string + role: + type: string + type: object + handler.updateProductRequest: + properties: + batch: + type: string + description: + type: string + expires_at: + type: string + name: + type: string + price_cents: + type: integer + seller_id: + type: string + stock: + type: integer + type: object handler.updateStatusRequest: properties: status: @@ -380,7 +555,108 @@ paths: summary: Registro de empresas tags: - Empresas + /api/companies/{id}: + delete: + parameters: + - description: Company ID + in: path + name: id + required: true + type: string + responses: + "204": + description: "" + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Remover empresa + tags: + - Empresas + get: + parameters: + - description: Company ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Company' + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Obter empresa + tags: + - Empresas + patch: + consumes: + - application/json + parameters: + - description: Company ID + in: path + name: id + required: true + type: string + - description: Campos para atualização + in: body + name: payload + required: true + schema: + $ref: '#/definitions/handler.updateCompanyRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Company' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Atualizar empresa + tags: + - Empresas /api/orders: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.Order' + type: array + security: + - BearerAuth: [] + summary: Listar pedidos + tags: + - Pedidos post: consumes: - application/json @@ -402,6 +678,33 @@ paths: tags: - Pedidos /api/orders/{id}: + delete: + parameters: + - description: Order ID + in: path + name: id + required: true + type: string + responses: + "204": + description: "" + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Remover pedido + tags: + - Pedidos get: parameters: - description: Order ID @@ -416,6 +719,8 @@ paths: description: OK schema: $ref: '#/definitions/domain.Order' + security: + - BearerAuth: [] summary: Consulta pedido tags: - Pedidos @@ -434,6 +739,8 @@ paths: description: Created schema: $ref: '#/definitions/domain.PaymentPreference' + security: + - BearerAuth: [] summary: Cria preferência de pagamento Mercado Pago com split nativo tags: - Pagamentos @@ -458,6 +765,8 @@ paths: responses: "204": description: "" + security: + - BearerAuth: [] summary: Atualiza status do pedido tags: - Pedidos @@ -495,6 +804,92 @@ paths: summary: Cadastro de produto com rastreabilidade de lote tags: - Produtos + /api/products/{id}: + delete: + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + responses: + "204": + description: "" + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Remover produto + tags: + - Produtos + get: + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Product' + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Obter produto + tags: + - Produtos + patch: + consumes: + - application/json + parameters: + - description: Product ID + in: path + name: id + required: true + type: string + - description: Campos para atualização + in: body + name: payload + required: true + schema: + $ref: '#/definitions/handler.updateProductRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Product' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "404": + description: Not Found + schema: + additionalProperties: + type: string + type: object + summary: Atualizar produto + tags: + - Produtos /api/v1/auth/login: post: consumes: @@ -563,6 +958,205 @@ paths: summary: Cadastro de usuário tags: - Autenticação + /api/v1/cart: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.CartSummary' + security: + - BearerAuth: [] + summary: Obter carrinho + tags: + - Carrinho + post: + consumes: + - application/json + parameters: + - description: Item do carrinho + in: body + name: payload + required: true + schema: + $ref: '#/definitions/handler.addCartItemRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/domain.CartSummary' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Adicionar item ao carrinho + tags: + - Carrinho + /api/v1/cart/{id}: + delete: + parameters: + - description: Cart item ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.CartSummary' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Remover item do carrinho + tags: + - Carrinho + /api/v1/companies/{id}/rating: + get: + parameters: + - description: Company ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.CompanyRating' + summary: Obter avaliação da empresa + tags: + - Empresas + /api/v1/companies/{id}/verify: + patch: + parameters: + - description: Company ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Company' + security: + - BearerAuth: [] + summary: Verificar empresa + tags: + - Empresas + /api/v1/companies/me: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.Company' + security: + - BearerAuth: [] + summary: Obter minha empresa + tags: + - Empresas + /api/v1/dashboard/admin: + get: + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.AdminDashboard' + security: + - BearerAuth: [] + summary: Dashboard do administrador + tags: + - Dashboard + /api/v1/dashboard/seller: + get: + parameters: + - description: Seller ID + in: query + name: seller_id + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.SellerDashboard' + security: + - BearerAuth: [] + summary: Dashboard do vendedor + tags: + - Dashboard + /api/v1/inventory: + get: + parameters: + - description: Dias para expiração + in: query + name: expires_in_days + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/domain.InventoryItem' + type: array + security: + - BearerAuth: [] + summary: Listar estoque + tags: + - Estoque + /api/v1/inventory/adjust: + post: + consumes: + - application/json + parameters: + - description: Ajuste de estoque + in: body + name: payload + required: true + schema: + $ref: '#/definitions/handler.inventoryAdjustRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/domain.InventoryItem' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Ajustar estoque + tags: + - Estoque /api/v1/payments/webhook: post: consumes: @@ -584,6 +1178,35 @@ paths: summary: Recebe notificações do Mercado Pago tags: - Pagamentos + /api/v1/reviews: + post: + consumes: + - application/json + parameters: + - description: Dados da avaliação + in: body + name: payload + required: true + schema: + $ref: '#/definitions/handler.createReviewRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/domain.Review' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Criar avaliação + tags: + - Avaliações /api/v1/shipments: post: consumes: @@ -602,6 +1225,8 @@ paths: description: Created schema: $ref: '#/definitions/domain.Shipment' + security: + - BearerAuth: [] summary: Gera guia de postagem/transporte tags: - Logistica @@ -620,6 +1245,8 @@ paths: description: OK schema: $ref: '#/definitions/domain.Shipment' + security: + - BearerAuth: [] summary: Rastreia entrega tags: - Logistica diff --git a/backend/internal/http/handler/handler.go b/backend/internal/http/handler/handler.go index 2864ec5..f6e3d58 100644 --- a/backend/internal/http/handler/handler.go +++ b/backend/internal/http/handler/handler.go @@ -160,6 +160,114 @@ func (h *Handler) ListCompanies(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, companies) } +// GetCompany godoc +// @Summary Obter empresa +// @Tags Empresas +// @Produce json +// @Param id path string true "Company ID" +// @Success 200 {object} domain.Company +// @Failure 404 {object} map[string]string +// @Router /api/companies/{id} [get] +func (h *Handler) GetCompany(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + company, err := h.svc.GetCompany(r.Context(), id) + if err != nil { + writeError(w, http.StatusNotFound, err) + return + } + + writeJSON(w, http.StatusOK, company) +} + +// UpdateCompany godoc +// @Summary Atualizar empresa +// @Tags Empresas +// @Accept json +// @Produce json +// @Param id path string true "Company ID" +// @Param payload body updateCompanyRequest true "Campos para atualização" +// @Success 200 {object} domain.Company +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/companies/{id} [patch] +func (h *Handler) UpdateCompany(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + var req updateCompanyRequest + if err := decodeJSON(r.Context(), r, &req); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + company, err := h.svc.GetCompany(r.Context(), id) + if err != nil { + writeError(w, http.StatusNotFound, err) + return + } + + if req.Role != nil { + company.Role = *req.Role + } + if req.CNPJ != nil { + company.CNPJ = *req.CNPJ + } + if req.CorporateName != nil { + company.CorporateName = *req.CorporateName + } + if req.LicenseNumber != nil { + company.LicenseNumber = *req.LicenseNumber + } + if req.IsVerified != nil { + company.IsVerified = *req.IsVerified + } + + if err := h.svc.UpdateCompany(r.Context(), company); err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + + writeJSON(w, http.StatusOK, company) +} + +// DeleteCompany godoc +// @Summary Remover empresa +// @Tags Empresas +// @Param id path string true "Company ID" +// @Success 204 "" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/companies/{id} [delete] +func (h *Handler) DeleteCompany(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + if err := h.svc.DeleteCompany(r.Context(), id); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// VerifyCompany godoc +// @Summary Verificar empresa +// @Tags Empresas +// @Security BearerAuth +// @Param id path string true "Company ID" +// @Success 200 {object} domain.Company +// @Router /api/v1/companies/{id}/verify [patch] // VerifyCompany toggles the verification flag for a company (admin only). func (h *Handler) VerifyCompany(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "/verify") { @@ -182,6 +290,13 @@ func (h *Handler) VerifyCompany(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, company) } +// GetMyCompany godoc +// @Summary Obter minha empresa +// @Tags Empresas +// @Security BearerAuth +// @Produce json +// @Success 200 {object} domain.Company +// @Router /api/v1/companies/me [get] // GetMyCompany returns the company linked to the authenticated user. func (h *Handler) GetMyCompany(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r.Context()) @@ -199,6 +314,13 @@ func (h *Handler) GetMyCompany(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, company) } +// GetCompanyRating godoc +// @Summary Obter avaliação da empresa +// @Tags Empresas +// @Produce json +// @Param id path string true "Company ID" +// @Success 200 {object} domain.CompanyRating +// @Router /api/v1/companies/{id}/rating [get] // GetCompanyRating exposes the average score for a company. func (h *Handler) GetCompanyRating(w http.ResponseWriter, r *http.Request) { if !strings.HasSuffix(r.URL.Path, "/rating") { @@ -269,6 +391,121 @@ func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, products) } +// GetProduct godoc +// @Summary Obter produto +// @Tags Produtos +// @Produce json +// @Param id path string true "Product ID" +// @Success 200 {object} domain.Product +// @Failure 404 {object} map[string]string +// @Router /api/products/{id} [get] +func (h *Handler) GetProduct(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + product, err := h.svc.GetProduct(r.Context(), id) + if err != nil { + writeError(w, http.StatusNotFound, err) + return + } + + writeJSON(w, http.StatusOK, product) +} + +// UpdateProduct godoc +// @Summary Atualizar produto +// @Tags Produtos +// @Accept json +// @Produce json +// @Param id path string true "Product ID" +// @Param payload body updateProductRequest true "Campos para atualização" +// @Success 200 {object} domain.Product +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/products/{id} [patch] +func (h *Handler) UpdateProduct(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + var req updateProductRequest + if err := decodeJSON(r.Context(), r, &req); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + product, err := h.svc.GetProduct(r.Context(), id) + if err != nil { + writeError(w, http.StatusNotFound, err) + return + } + + if req.SellerID != nil { + product.SellerID = *req.SellerID + } + if req.Name != nil { + product.Name = *req.Name + } + if req.Description != nil { + product.Description = *req.Description + } + if req.Batch != nil { + product.Batch = *req.Batch + } + if req.ExpiresAt != nil { + product.ExpiresAt = *req.ExpiresAt + } + if req.PriceCents != nil { + product.PriceCents = *req.PriceCents + } + if req.Stock != nil { + product.Stock = *req.Stock + } + + if err := h.svc.UpdateProduct(r.Context(), product); err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + + writeJSON(w, http.StatusOK, product) +} + +// DeleteProduct godoc +// @Summary Remover produto +// @Tags Produtos +// @Param id path string true "Product ID" +// @Success 204 "" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/products/{id} [delete] +func (h *Handler) DeleteProduct(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + if err := h.svc.DeleteProduct(r.Context(), id); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// ListInventory godoc +// @Summary Listar estoque +// @Tags Estoque +// @Security BearerAuth +// @Produce json +// @Param expires_in_days query int false "Dias para expiração" +// @Success 200 {array} domain.InventoryItem +// @Router /api/v1/inventory [get] // ListInventory exposes stock with expiring batch filters. func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) { var filter domain.InventoryFilter @@ -291,6 +528,16 @@ func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, inventory) } +// AdjustInventory godoc +// @Summary Ajustar estoque +// @Tags Estoque +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param payload body inventoryAdjustRequest true "Ajuste de estoque" +// @Success 200 {object} domain.InventoryItem +// @Failure 400 {object} map[string]string +// @Router /api/v1/inventory/adjust [post] // AdjustInventory handles manual stock corrections. func (h *Handler) AdjustInventory(w http.ResponseWriter, r *http.Request) { var req inventoryAdjustRequest @@ -349,9 +596,27 @@ func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, order) } +// ListOrders godoc +// @Summary Listar pedidos +// @Tags Pedidos +// @Security BearerAuth +// @Produce json +// @Success 200 {array} domain.Order +// @Router /api/orders [get] +func (h *Handler) ListOrders(w http.ResponseWriter, r *http.Request) { + orders, err := h.svc.ListOrders(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, err) + return + } + + writeJSON(w, http.StatusOK, orders) +} + // GetOrder godoc // @Summary Consulta pedido // @Tags Pedidos +// @Security BearerAuth // @Produce json // @Param id path string true "Order ID" // @Success 200 {object} domain.Order @@ -375,6 +640,7 @@ func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) { // UpdateOrderStatus godoc // @Summary Atualiza status do pedido // @Tags Pedidos +// @Security BearerAuth // @Accept json // @Produce json // @Param id path string true "Order ID" @@ -407,6 +673,40 @@ func (h *Handler) UpdateOrderStatus(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } +// DeleteOrder godoc +// @Summary Remover pedido +// @Tags Pedidos +// @Security BearerAuth +// @Param id path string true "Order ID" +// @Success 204 "" +// @Failure 400 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Router /api/orders/{id} [delete] +func (h *Handler) DeleteOrder(w http.ResponseWriter, r *http.Request) { + id, err := parseUUIDFromPath(r.URL.Path) + if err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + if err := h.svc.DeleteOrder(r.Context(), id); err != nil { + writeError(w, http.StatusBadRequest, err) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// CreateReview godoc +// @Summary Criar avaliação +// @Tags Avaliações +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param payload body createReviewRequest true "Dados da avaliação" +// @Success 201 {object} domain.Review +// @Failure 400 {object} map[string]string +// @Router /api/v1/reviews [post] // CreateReview allows buyers to rate the seller after delivery. func (h *Handler) CreateReview(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r.Context()) @@ -430,6 +730,16 @@ func (h *Handler) CreateReview(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, review) } +// AddToCart godoc +// @Summary Adicionar item ao carrinho +// @Tags Carrinho +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param payload body addCartItemRequest true "Item do carrinho" +// @Success 201 {object} domain.CartSummary +// @Failure 400 {object} map[string]string +// @Router /api/v1/cart [post] // AddToCart appends an item to the authenticated buyer cart respecting stock. func (h *Handler) AddToCart(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r.Context()) @@ -453,6 +763,13 @@ func (h *Handler) AddToCart(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, summary) } +// GetCart godoc +// @Summary Obter carrinho +// @Tags Carrinho +// @Security BearerAuth +// @Produce json +// @Success 200 {object} domain.CartSummary +// @Router /api/v1/cart [get] // GetCart returns cart contents and totals for the authenticated buyer. func (h *Handler) GetCart(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r.Context()) @@ -469,6 +786,14 @@ func (h *Handler) GetCart(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, summary) } +// DeleteCartItem godoc +// @Summary Remover item do carrinho +// @Tags Carrinho +// @Security BearerAuth +// @Param id path string true "Cart item ID" +// @Success 200 {object} domain.CartSummary +// @Failure 400 {object} map[string]string +// @Router /api/v1/cart/{id} [delete] // DeleteCartItem removes a product from the cart and returns the updated totals. func (h *Handler) DeleteCartItem(w http.ResponseWriter, r *http.Request) { claims, ok := middleware.GetClaims(r.Context()) @@ -495,6 +820,7 @@ func (h *Handler) DeleteCartItem(w http.ResponseWriter, r *http.Request) { // CreatePaymentPreference godoc // @Summary Cria preferência de pagamento Mercado Pago com split nativo // @Tags Pagamentos +// @Security BearerAuth // @Produce json // @Param id path string true "Order ID" // @Success 201 {object} domain.PaymentPreference @@ -523,6 +849,7 @@ func (h *Handler) CreatePaymentPreference(w http.ResponseWriter, r *http.Request // CreateShipment godoc // @Summary Gera guia de postagem/transporte // @Tags Logistica +// @Security BearerAuth // @Accept json // @Produce json // @Param shipment body createShipmentRequest true "Dados de envio" @@ -553,6 +880,7 @@ func (h *Handler) CreateShipment(w http.ResponseWriter, r *http.Request) { // GetShipmentByOrderID godoc // @Summary Rastreia entrega // @Tags Logistica +// @Security BearerAuth // @Produce json // @Param order_id path string true "Order ID" // @Success 200 {object} domain.Shipment @@ -598,6 +926,13 @@ func (h *Handler) HandlePaymentWebhook(w http.ResponseWriter, r *http.Request) { } // GetSellerDashboard aggregates KPIs for the authenticated seller or the requested company. +// @Summary Dashboard do vendedor +// @Tags Dashboard +// @Security BearerAuth +// @Produce json +// @Param seller_id query string false "Seller ID" +// @Success 200 {object} domain.SellerDashboard +// @Router /api/v1/dashboard/seller [get] func (h *Handler) GetSellerDashboard(w http.ResponseWriter, r *http.Request) { requester, err := getRequester(r) if err != nil { @@ -635,6 +970,12 @@ func (h *Handler) GetSellerDashboard(w http.ResponseWriter, r *http.Request) { } // GetAdminDashboard exposes platform-wide aggregated metrics. +// @Summary Dashboard do administrador +// @Tags Dashboard +// @Security BearerAuth +// @Produce json +// @Success 200 {object} domain.AdminDashboard +// @Router /api/v1/dashboard/admin [get] func (h *Handler) GetAdminDashboard(w http.ResponseWriter, r *http.Request) { requester, err := getRequester(r) if err != nil { @@ -1024,6 +1365,14 @@ type registerCompanyRequest struct { LicenseNumber string `json:"license_number"` } +type updateCompanyRequest struct { + Role *string `json:"role,omitempty"` + CNPJ *string `json:"cnpj,omitempty"` + CorporateName *string `json:"corporate_name,omitempty"` + LicenseNumber *string `json:"license_number,omitempty"` + IsVerified *bool `json:"is_verified,omitempty"` +} + type registerProductRequest struct { SellerID uuid.UUID `json:"seller_id"` Name string `json:"name"` @@ -1034,6 +1383,16 @@ type registerProductRequest struct { Stock int64 `json:"stock"` } +type updateProductRequest struct { + SellerID *uuid.UUID `json:"seller_id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + Batch *string `json:"batch,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + PriceCents *int64 `json:"price_cents,omitempty"` + Stock *int64 `json:"stock,omitempty"` +} + type createOrderRequest struct { BuyerID uuid.UUID `json:"buyer_id"` SellerID uuid.UUID `json:"seller_id"` diff --git a/backend/internal/repository/postgres/postgres.go b/backend/internal/repository/postgres/postgres.go index a1d28ca..5cc534d 100644 --- a/backend/internal/repository/postgres/postgres.go +++ b/backend/internal/repository/postgres/postgres.go @@ -74,6 +74,53 @@ WHERE id = :id` return nil } +func (r *Repository) DeleteCompany(ctx context.Context, id uuid.UUID) error { + var count int + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM users WHERE company_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("company has related users") + } + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM products WHERE seller_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("company has related products") + } + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM orders WHERE buyer_id = $1 OR seller_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("company has related orders") + } + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM reviews WHERE buyer_id = $1 OR seller_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("company has related reviews") + } + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM cart_items WHERE buyer_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("company has related cart items") + } + + res, err := r.db.ExecContext(ctx, `DELETE FROM companies WHERE id = $1`, id) + if err != nil { + return err + } + rows, err := res.RowsAffected() + if err != nil { + return err + } + if rows == 0 { + return errors.New("company not found") + } + return nil +} + func (r *Repository) CreateProduct(ctx context.Context, product *domain.Product) error { now := time.Now().UTC() product.CreatedAt = now @@ -104,6 +151,68 @@ func (r *Repository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Prod return &product, nil } +func (r *Repository) UpdateProduct(ctx context.Context, product *domain.Product) error { + product.UpdatedAt = time.Now().UTC() + + query := `UPDATE products +SET seller_id = :seller_id, name = :name, description = :description, batch = :batch, expires_at = :expires_at, price_cents = :price_cents, stock = :stock, updated_at = :updated_at +WHERE id = :id` + + res, err := r.db.NamedExecContext(ctx, query, product) + if err != nil { + return err + } + rows, err := res.RowsAffected() + if err != nil { + return err + } + if rows == 0 { + return errors.New("product not found") + } + return nil +} + +func (r *Repository) DeleteProduct(ctx context.Context, id uuid.UUID) error { + var count int + if err := r.db.GetContext(ctx, &count, `SELECT COUNT(*) FROM order_items WHERE product_id = $1`, id); err != nil { + return err + } + if count > 0 { + return errors.New("product has related orders") + } + + tx, err := r.db.BeginTxx(ctx, nil) + if err != nil { + return err + } + + if _, err := tx.ExecContext(ctx, `DELETE FROM inventory_adjustments WHERE product_id = $1`, id); err != nil { + _ = tx.Rollback() + return err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM cart_items WHERE product_id = $1`, id); err != nil { + _ = tx.Rollback() + return err + } + + res, err := tx.ExecContext(ctx, `DELETE FROM products WHERE id = $1`, id) + if err != nil { + _ = tx.Rollback() + return err + } + rows, err := res.RowsAffected() + if err != nil { + _ = tx.Rollback() + return err + } + if rows == 0 { + _ = tx.Rollback() + return errors.New("product not found") + } + + return tx.Commit() +} + func (r *Repository) CreateOrder(ctx context.Context, order *domain.Order) error { now := time.Now().UTC() order.CreatedAt = now @@ -135,6 +244,62 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)` return tx.Commit() } +func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) { + var rows []struct { + ID uuid.UUID `db:"id"` + BuyerID uuid.UUID `db:"buyer_id"` + SellerID uuid.UUID `db:"seller_id"` + Status domain.OrderStatus `db:"status"` + TotalCents int64 `db:"total_cents"` + ShippingRecipientName string `db:"shipping_recipient_name"` + ShippingStreet string `db:"shipping_street"` + ShippingNumber string `db:"shipping_number"` + ShippingComplement string `db:"shipping_complement"` + ShippingDistrict string `db:"shipping_district"` + ShippingCity string `db:"shipping_city"` + ShippingState string `db:"shipping_state"` + ShippingZipCode string `db:"shipping_zip_code"` + ShippingCountry string `db:"shipping_country"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + } + query := `SELECT id, buyer_id, seller_id, status, total_cents, shipping_recipient_name, shipping_street, shipping_number, shipping_complement, shipping_district, shipping_city, shipping_state, shipping_zip_code, shipping_country, created_at, updated_at FROM orders ORDER BY created_at DESC` + if err := r.db.SelectContext(ctx, &rows, query); err != nil { + return nil, err + } + + orders := make([]domain.Order, 0, len(rows)) + itemQuery := `SELECT id, order_id, product_id, quantity, unit_cents, batch, expires_at FROM order_items WHERE order_id = $1` + for _, row := range rows { + var items []domain.OrderItem + if err := r.db.SelectContext(ctx, &items, itemQuery, row.ID); err != nil { + return nil, err + } + orders = append(orders, domain.Order{ + ID: row.ID, + BuyerID: row.BuyerID, + SellerID: row.SellerID, + Status: row.Status, + TotalCents: row.TotalCents, + Items: items, + Shipping: domain.ShippingAddress{ + RecipientName: row.ShippingRecipientName, + Street: row.ShippingStreet, + Number: row.ShippingNumber, + Complement: row.ShippingComplement, + District: row.ShippingDistrict, + City: row.ShippingCity, + State: row.ShippingState, + ZipCode: row.ShippingZipCode, + Country: row.ShippingCountry, + }, + CreatedAt: row.CreatedAt, + UpdatedAt: row.UpdatedAt, + }) + } + return orders, nil +} + func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { var row struct { ID uuid.UUID `db:"id"` @@ -204,6 +369,43 @@ func (r *Repository) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status return nil } +func (r *Repository) DeleteOrder(ctx context.Context, id uuid.UUID) error { + tx, err := r.db.BeginTxx(ctx, nil) + if err != nil { + return err + } + + if _, err := tx.ExecContext(ctx, `DELETE FROM reviews WHERE order_id = $1`, id); err != nil { + _ = tx.Rollback() + return err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM shipments WHERE order_id = $1`, id); err != nil { + _ = tx.Rollback() + return err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM order_items WHERE order_id = $1`, id); err != nil { + _ = tx.Rollback() + return err + } + + res, err := tx.ExecContext(ctx, `DELETE FROM orders WHERE id = $1`, id) + if err != nil { + _ = tx.Rollback() + return err + } + rows, err := res.RowsAffected() + if err != nil { + _ = tx.Rollback() + return err + } + if rows == 0 { + _ = tx.Rollback() + return errors.New("order not found") + } + + return tx.Commit() +} + func (r *Repository) CreateShipment(ctx context.Context, shipment *domain.Shipment) error { now := time.Now().UTC() shipment.CreatedAt = now diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index ca2818e..79f95e0 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -64,19 +64,27 @@ func New(cfg config.Config) (*Server, error) { mux.Handle("POST /api/companies", chain(http.HandlerFunc(h.CreateCompany), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/companies", chain(http.HandlerFunc(h.ListCompanies), middleware.Logger, middleware.Gzip)) + mux.Handle("GET /api/companies/", chain(http.HandlerFunc(h.GetCompany), middleware.Logger, middleware.Gzip)) + mux.Handle("PATCH /api/companies/", chain(http.HandlerFunc(h.UpdateCompany), middleware.Logger, middleware.Gzip)) + mux.Handle("DELETE /api/companies/", chain(http.HandlerFunc(h.DeleteCompany), middleware.Logger, middleware.Gzip)) mux.Handle("PATCH /api/v1/companies/", chain(http.HandlerFunc(h.VerifyCompany), middleware.Logger, middleware.Gzip, adminOnly)) mux.Handle("GET /api/v1/companies/me", chain(http.HandlerFunc(h.GetMyCompany), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/v1/companies/", chain(http.HandlerFunc(h.GetCompanyRating), middleware.Logger, middleware.Gzip)) mux.Handle("POST /api/products", chain(http.HandlerFunc(h.CreateProduct), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/products", chain(http.HandlerFunc(h.ListProducts), middleware.Logger, middleware.Gzip)) + mux.Handle("GET /api/products/", chain(http.HandlerFunc(h.GetProduct), middleware.Logger, middleware.Gzip)) + mux.Handle("PATCH /api/products/", chain(http.HandlerFunc(h.UpdateProduct), middleware.Logger, middleware.Gzip)) + mux.Handle("DELETE /api/products/", chain(http.HandlerFunc(h.DeleteProduct), middleware.Logger, middleware.Gzip)) mux.Handle("GET /api/v1/inventory", chain(http.HandlerFunc(h.ListInventory), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/inventory/adjust", chain(http.HandlerFunc(h.AdjustInventory), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip, auth)) + mux.Handle("GET /api/orders", chain(http.HandlerFunc(h.ListOrders), middleware.Logger, middleware.Gzip, auth)) mux.Handle("GET /api/orders/", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip, auth)) mux.Handle("PATCH /api/orders/", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip, auth)) + mux.Handle("DELETE /api/orders/", chain(http.HandlerFunc(h.DeleteOrder), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/orders/", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip, auth)) mux.Handle("POST /api/v1/shipments", chain(http.HandlerFunc(h.CreateShipment), middleware.Logger, middleware.Gzip, auth)) diff --git a/backend/internal/usecase/usecase.go b/backend/internal/usecase/usecase.go index afabaff..3076f56 100644 --- a/backend/internal/usecase/usecase.go +++ b/backend/internal/usecase/usecase.go @@ -20,16 +20,21 @@ type Repository interface { ListCompanies(ctx context.Context) ([]domain.Company, error) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) UpdateCompany(ctx context.Context, company *domain.Company) error + DeleteCompany(ctx context.Context, id uuid.UUID) error CreateProduct(ctx context.Context, product *domain.Product) error ListProducts(ctx context.Context) ([]domain.Product, error) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) + UpdateProduct(ctx context.Context, product *domain.Product) error + DeleteProduct(ctx context.Context, id uuid.UUID) error AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) CreateOrder(ctx context.Context, order *domain.Order) error + ListOrders(ctx context.Context) ([]domain.Order, error) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error + DeleteOrder(ctx context.Context, id uuid.UUID) error CreateShipment(ctx context.Context, shipment *domain.Shipment) error GetShipmentByOrderID(ctx context.Context, orderID uuid.UUID) (*domain.Shipment, error) @@ -89,6 +94,14 @@ func (s *Service) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company return s.repo.GetCompany(ctx, id) } +func (s *Service) UpdateCompany(ctx context.Context, company *domain.Company) error { + return s.repo.UpdateCompany(ctx, company) +} + +func (s *Service) DeleteCompany(ctx context.Context, id uuid.UUID) error { + return s.repo.DeleteCompany(ctx, id) +} + func (s *Service) RegisterProduct(ctx context.Context, product *domain.Product) error { product.ID = uuid.Must(uuid.NewV7()) return s.repo.CreateProduct(ctx, product) @@ -98,6 +111,18 @@ func (s *Service) ListProducts(ctx context.Context) ([]domain.Product, error) { return s.repo.ListProducts(ctx) } +func (s *Service) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) { + return s.repo.GetProduct(ctx, id) +} + +func (s *Service) UpdateProduct(ctx context.Context, product *domain.Product) error { + return s.repo.UpdateProduct(ctx, product) +} + +func (s *Service) DeleteProduct(ctx context.Context, id uuid.UUID) error { + return s.repo.DeleteProduct(ctx, id) +} + func (s *Service) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) { return s.repo.ListInventory(ctx, filter) } @@ -112,6 +137,10 @@ func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error { return s.repo.CreateOrder(ctx, order) } +func (s *Service) ListOrders(ctx context.Context) ([]domain.Order, error) { + return s.repo.ListOrders(ctx) +} + func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { return s.repo.GetOrder(ctx, id) } @@ -120,6 +149,10 @@ func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status do return s.repo.UpdateOrderStatus(ctx, id, status) } +func (s *Service) DeleteOrder(ctx context.Context, id uuid.UUID) error { + return s.repo.DeleteOrder(ctx, id) +} + // CreateShipment persists a freight label for an order if not already present. func (s *Service) CreateShipment(ctx context.Context, shipment *domain.Shipment) error { if _, err := s.repo.GetOrder(ctx, shipment.OrderID); err != nil {