diff --git a/backend-go/.gitignore b/backend-go/.gitignore
new file mode 100644
index 0000000..5e56e04
--- /dev/null
+++ b/backend-go/.gitignore
@@ -0,0 +1 @@
+/bin
diff --git a/backend-go/Dockerfile b/backend-go/Dockerfile
new file mode 100644
index 0000000..5854276
--- /dev/null
+++ b/backend-go/Dockerfile
@@ -0,0 +1,18 @@
+# syntax=docker/dockerfile:1
+FROM golang:1.24 AS builder
+WORKDIR /app
+
+COPY go.mod go.sum ./
+RUN go mod download
+
+COPY . .
+
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "-s -w" -o /out/performance-core ./cmd/api
+
+FROM gcr.io/distroless/base-debian12:nonroot
+WORKDIR /app
+
+COPY --from=builder /out/performance-core /app/performance-core
+
+EXPOSE 8080
+ENTRYPOINT ["/app/performance-core"]
diff --git a/backend-go/README.md b/backend-go/README.md
new file mode 100644
index 0000000..3647538
--- /dev/null
+++ b/backend-go/README.md
@@ -0,0 +1,27 @@
+# SaveInMed Performance Core (Go)
+
+Backend em Go 1.24 focado em alta performance para o marketplace farmacêutico B2B.
+
+## Funcionalidades
+- Gestão de empresas com separação de papéis (farmácia, distribuidora, administrador).
+- Catálogo de produtos com lote e validade obrigatórios.
+- Pedidos com ciclo Pendente → Pago → Faturado → Entregue.
+- Geração de preferência de pagamento Mercado Pago com split e retenção de comissão.
+- Respostas JSON com `json-iterator` e compressão gzip.
+- Swagger disponível em `/swagger/index.html`.
+
+## Execução local
+```bash
+export DATABASE_URL=postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable
+cd backend-go
+# gerar swagger (já versionado)
+./bin/swag init --dir ./cmd/api,./internal/http/handler,./internal/domain --output ./docs --parseDependency --parseInternal
+# executar API
+go run ./cmd/api
+```
+
+## Docker
+```bash
+docker build -t saveinmed-performance-core:dev .
+docker run -p 8080:8080 -e DATABASE_URL=postgres://... saveinmed-performance-core:dev
+```
diff --git a/backend-go/cmd/api/main.go b/backend-go/cmd/api/main.go
new file mode 100644
index 0000000..7255717
--- /dev/null
+++ b/backend-go/cmd/api/main.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+
+ _ "github.com/jackc/pgx/v5/stdlib"
+
+ "github.com/saveinmed/backend-go/docs"
+ "github.com/saveinmed/backend-go/internal/config"
+ "github.com/saveinmed/backend-go/internal/server"
+)
+
+// @title SaveInMed Performance Core API
+// @version 1.0
+// @description API REST B2B para marketplace farmacêutico com split de pagamento e rastreabilidade.
+// @BasePath /
+// @Schemes http
+// @contact.name Engenharia SaveInMed
+// @contact.email devops@saveinmed.com
+func main() {
+ cfg := config.Load()
+
+ // swagger metadata overrides
+ docs.SwaggerInfo.Title = cfg.AppName
+ docs.SwaggerInfo.BasePath = "/"
+
+ srv, err := server.New(cfg)
+ if err != nil {
+ log.Fatalf("boot failure: %v", err)
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+ defer stop()
+
+ if err := srv.Start(ctx); err != nil {
+ log.Printf("server stopped: %v", err)
+ os.Exit(1)
+ }
+}
diff --git a/backend-go/docs/docs.go b/backend-go/docs/docs.go
new file mode 100644
index 0000000..a3ae9f7
--- /dev/null
+++ b/backend-go/docs/docs.go
@@ -0,0 +1,501 @@
+// Package docs Code generated by swaggo/swag. DO NOT EDIT
+package docs
+
+import "github.com/swaggo/swag"
+
+const docTemplate = `{
+ "schemes": {{ marshal .Schemes }},
+ "swagger": "2.0",
+ "info": {
+ "description": "{{escape .Description}}",
+ "title": "{{.Title}}",
+ "contact": {
+ "name": "Engenharia SaveInMed",
+ "email": "devops@saveinmed.com"
+ },
+ "version": "{{.Version}}"
+ },
+ "host": "{{.Host}}",
+ "basePath": "{{.BasePath}}",
+ "paths": {
+ "/api/companies": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Empresas"
+ ],
+ "summary": "Lista empresas",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.Company"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "description": "Cadastra farmácia, distribuidora ou administrador com CNPJ e licença sanitária.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Empresas"
+ ],
+ "summary": "Registro de empresas",
+ "parameters": [
+ {
+ "description": "Dados da empresa",
+ "name": "company",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.registerCompanyRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Company"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders": {
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Criação de pedido com split",
+ "parameters": [
+ {
+ "description": "Pedido",
+ "name": "order",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.createOrderRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Order"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Consulta pedido",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/domain.Order"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}/payment": {
+ "post": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pagamentos"
+ ],
+ "summary": "Cria preferência de pagamento Mercado Pago com split nativo",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.PaymentPreference"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}/status": {
+ "patch": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Atualiza status do pedido",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Novo status",
+ "name": "status",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.updateStatusRequest"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": ""
+ }
+ }
+ }
+ },
+ "/api/products": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Produtos"
+ ],
+ "summary": "Lista catálogo com lote e validade",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.Product"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Produtos"
+ ],
+ "summary": "Cadastro de produto com rastreabilidade de lote",
+ "parameters": [
+ {
+ "description": "Produto",
+ "name": "product",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.registerProductRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Product"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "domain.Company": {
+ "type": "object",
+ "properties": {
+ "cnpj": {
+ "type": "string"
+ },
+ "corporate_name": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "role": {
+ "description": "pharmacy, distributor, admin",
+ "type": "string"
+ },
+ "sanitary_license": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "domain.Order": {
+ "type": "object",
+ "properties": {
+ "buyer_id": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.OrderItem"
+ }
+ },
+ "seller_id": {
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/definitions/domain.OrderStatus"
+ },
+ "total_cents": {
+ "type": "integer"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "domain.OrderItem": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "string"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "order_id": {
+ "type": "string"
+ },
+ "product_id": {
+ "type": "string"
+ },
+ "quantity": {
+ "type": "integer"
+ },
+ "unit_cents": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.OrderStatus": {
+ "type": "string",
+ "enum": [
+ "Pendente",
+ "Pago",
+ "Faturado",
+ "Entregue"
+ ],
+ "x-enum-varnames": [
+ "OrderStatusPending",
+ "OrderStatusPaid",
+ "OrderStatusInvoiced",
+ "OrderStatusDelivered"
+ ]
+ },
+ "domain.PaymentPreference": {
+ "type": "object",
+ "properties": {
+ "commission_pct": {
+ "type": "number"
+ },
+ "gateway": {
+ "type": "string"
+ },
+ "marketplace_fee": {
+ "type": "integer"
+ },
+ "order_id": {
+ "type": "string"
+ },
+ "payment_url": {
+ "type": "string"
+ },
+ "seller_receivable": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.Product": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "price_cents": {
+ "type": "integer"
+ },
+ "seller_id": {
+ "type": "string"
+ },
+ "stock": {
+ "type": "integer"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.createOrderRequest": {
+ "type": "object",
+ "properties": {
+ "buyer_id": {
+ "type": "string"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.OrderItem"
+ }
+ },
+ "seller_id": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.registerCompanyRequest": {
+ "type": "object",
+ "properties": {
+ "cnpj": {
+ "type": "string"
+ },
+ "corporate_name": {
+ "type": "string"
+ },
+ "role": {
+ "type": "string"
+ },
+ "sanitary_license": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.registerProductRequest": {
+ "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": {
+ "status": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}`
+
+// SwaggerInfo holds exported Swagger Info so clients can modify it
+var SwaggerInfo = &swag.Spec{
+ Version: "1.0",
+ Host: "",
+ BasePath: "/",
+ Schemes: []string{"http"},
+ Title: "SaveInMed Performance Core API",
+ Description: "API REST B2B para marketplace farmacêutico com split de pagamento e rastreabilidade.",
+ InfoInstanceName: "swagger",
+ SwaggerTemplate: docTemplate,
+ LeftDelim: "{{",
+ RightDelim: "}}",
+}
+
+func init() {
+ swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
+}
diff --git a/backend-go/docs/swagger.json b/backend-go/docs/swagger.json
new file mode 100644
index 0000000..26e570b
--- /dev/null
+++ b/backend-go/docs/swagger.json
@@ -0,0 +1,479 @@
+{
+ "schemes": [
+ "http"
+ ],
+ "swagger": "2.0",
+ "info": {
+ "description": "API REST B2B para marketplace farmacêutico com split de pagamento e rastreabilidade.",
+ "title": "SaveInMed Performance Core API",
+ "contact": {
+ "name": "Engenharia SaveInMed",
+ "email": "devops@saveinmed.com"
+ },
+ "version": "1.0"
+ },
+ "basePath": "/",
+ "paths": {
+ "/api/companies": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Empresas"
+ ],
+ "summary": "Lista empresas",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.Company"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "description": "Cadastra farmácia, distribuidora ou administrador com CNPJ e licença sanitária.",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Empresas"
+ ],
+ "summary": "Registro de empresas",
+ "parameters": [
+ {
+ "description": "Dados da empresa",
+ "name": "company",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.registerCompanyRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Company"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders": {
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Criação de pedido com split",
+ "parameters": [
+ {
+ "description": "Pedido",
+ "name": "order",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.createOrderRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Order"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Consulta pedido",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "$ref": "#/definitions/domain.Order"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}/payment": {
+ "post": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pagamentos"
+ ],
+ "summary": "Cria preferência de pagamento Mercado Pago com split nativo",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.PaymentPreference"
+ }
+ }
+ }
+ }
+ },
+ "/api/orders/{id}/status": {
+ "patch": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Pedidos"
+ ],
+ "summary": "Atualiza status do pedido",
+ "parameters": [
+ {
+ "type": "string",
+ "description": "Order ID",
+ "name": "id",
+ "in": "path",
+ "required": true
+ },
+ {
+ "description": "Novo status",
+ "name": "status",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.updateStatusRequest"
+ }
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": ""
+ }
+ }
+ }
+ },
+ "/api/products": {
+ "get": {
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Produtos"
+ ],
+ "summary": "Lista catálogo com lote e validade",
+ "responses": {
+ "200": {
+ "description": "OK",
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.Product"
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "tags": [
+ "Produtos"
+ ],
+ "summary": "Cadastro de produto com rastreabilidade de lote",
+ "parameters": [
+ {
+ "description": "Produto",
+ "name": "product",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/handler.registerProductRequest"
+ }
+ }
+ ],
+ "responses": {
+ "201": {
+ "description": "Created",
+ "schema": {
+ "$ref": "#/definitions/domain.Product"
+ }
+ }
+ }
+ }
+ }
+ },
+ "definitions": {
+ "domain.Company": {
+ "type": "object",
+ "properties": {
+ "cnpj": {
+ "type": "string"
+ },
+ "corporate_name": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "role": {
+ "description": "pharmacy, distributor, admin",
+ "type": "string"
+ },
+ "sanitary_license": {
+ "type": "string"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "domain.Order": {
+ "type": "object",
+ "properties": {
+ "buyer_id": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.OrderItem"
+ }
+ },
+ "seller_id": {
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/definitions/domain.OrderStatus"
+ },
+ "total_cents": {
+ "type": "integer"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "domain.OrderItem": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "string"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "order_id": {
+ "type": "string"
+ },
+ "product_id": {
+ "type": "string"
+ },
+ "quantity": {
+ "type": "integer"
+ },
+ "unit_cents": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.OrderStatus": {
+ "type": "string",
+ "enum": [
+ "Pendente",
+ "Pago",
+ "Faturado",
+ "Entregue"
+ ],
+ "x-enum-varnames": [
+ "OrderStatusPending",
+ "OrderStatusPaid",
+ "OrderStatusInvoiced",
+ "OrderStatusDelivered"
+ ]
+ },
+ "domain.PaymentPreference": {
+ "type": "object",
+ "properties": {
+ "commission_pct": {
+ "type": "number"
+ },
+ "gateway": {
+ "type": "string"
+ },
+ "marketplace_fee": {
+ "type": "integer"
+ },
+ "order_id": {
+ "type": "string"
+ },
+ "payment_url": {
+ "type": "string"
+ },
+ "seller_receivable": {
+ "type": "integer"
+ }
+ }
+ },
+ "domain.Product": {
+ "type": "object",
+ "properties": {
+ "batch": {
+ "type": "string"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "expires_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "price_cents": {
+ "type": "integer"
+ },
+ "seller_id": {
+ "type": "string"
+ },
+ "stock": {
+ "type": "integer"
+ },
+ "updated_at": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.createOrderRequest": {
+ "type": "object",
+ "properties": {
+ "buyer_id": {
+ "type": "string"
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/domain.OrderItem"
+ }
+ },
+ "seller_id": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.registerCompanyRequest": {
+ "type": "object",
+ "properties": {
+ "cnpj": {
+ "type": "string"
+ },
+ "corporate_name": {
+ "type": "string"
+ },
+ "role": {
+ "type": "string"
+ },
+ "sanitary_license": {
+ "type": "string"
+ }
+ }
+ },
+ "handler.registerProductRequest": {
+ "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": {
+ "status": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend-go/docs/swagger.yaml b/backend-go/docs/swagger.yaml
new file mode 100644
index 0000000..684829f
--- /dev/null
+++ b/backend-go/docs/swagger.yaml
@@ -0,0 +1,315 @@
+basePath: /
+definitions:
+ domain.Company:
+ properties:
+ cnpj:
+ type: string
+ corporate_name:
+ type: string
+ created_at:
+ type: string
+ id:
+ type: string
+ role:
+ description: pharmacy, distributor, admin
+ type: string
+ sanitary_license:
+ type: string
+ updated_at:
+ type: string
+ type: object
+ domain.Order:
+ properties:
+ buyer_id:
+ type: string
+ created_at:
+ type: string
+ id:
+ type: string
+ items:
+ items:
+ $ref: '#/definitions/domain.OrderItem'
+ type: array
+ seller_id:
+ type: string
+ status:
+ $ref: '#/definitions/domain.OrderStatus'
+ total_cents:
+ type: integer
+ updated_at:
+ type: string
+ type: object
+ domain.OrderItem:
+ properties:
+ batch:
+ type: string
+ expires_at:
+ type: string
+ id:
+ type: string
+ order_id:
+ type: string
+ product_id:
+ type: string
+ quantity:
+ type: integer
+ unit_cents:
+ type: integer
+ type: object
+ domain.OrderStatus:
+ enum:
+ - Pendente
+ - Pago
+ - Faturado
+ - Entregue
+ type: string
+ x-enum-varnames:
+ - OrderStatusPending
+ - OrderStatusPaid
+ - OrderStatusInvoiced
+ - OrderStatusDelivered
+ domain.PaymentPreference:
+ properties:
+ commission_pct:
+ type: number
+ gateway:
+ type: string
+ marketplace_fee:
+ type: integer
+ order_id:
+ type: string
+ payment_url:
+ type: string
+ seller_receivable:
+ type: integer
+ type: object
+ domain.Product:
+ properties:
+ batch:
+ type: string
+ created_at:
+ type: string
+ description:
+ type: string
+ expires_at:
+ type: string
+ id:
+ type: string
+ name:
+ type: string
+ price_cents:
+ type: integer
+ seller_id:
+ type: string
+ stock:
+ type: integer
+ updated_at:
+ type: string
+ type: object
+ handler.createOrderRequest:
+ properties:
+ buyer_id:
+ type: string
+ items:
+ items:
+ $ref: '#/definitions/domain.OrderItem'
+ type: array
+ seller_id:
+ type: string
+ type: object
+ handler.registerCompanyRequest:
+ properties:
+ cnpj:
+ type: string
+ corporate_name:
+ type: string
+ role:
+ type: string
+ sanitary_license:
+ type: string
+ type: object
+ handler.registerProductRequest:
+ 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:
+ type: string
+ type: object
+info:
+ contact:
+ email: devops@saveinmed.com
+ name: Engenharia SaveInMed
+ description: API REST B2B para marketplace farmacêutico com split de pagamento e
+ rastreabilidade.
+ title: SaveInMed Performance Core API
+ version: "1.0"
+paths:
+ /api/companies:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/domain.Company'
+ type: array
+ summary: Lista empresas
+ tags:
+ - Empresas
+ post:
+ consumes:
+ - application/json
+ description: Cadastra farmácia, distribuidora ou administrador com CNPJ e licença
+ sanitária.
+ parameters:
+ - description: Dados da empresa
+ in: body
+ name: company
+ required: true
+ schema:
+ $ref: '#/definitions/handler.registerCompanyRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/domain.Company'
+ summary: Registro de empresas
+ tags:
+ - Empresas
+ /api/orders:
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Pedido
+ in: body
+ name: order
+ required: true
+ schema:
+ $ref: '#/definitions/handler.createOrderRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/domain.Order'
+ summary: Criação de pedido com split
+ tags:
+ - Pedidos
+ /api/orders/{id}:
+ get:
+ parameters:
+ - description: Order ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ $ref: '#/definitions/domain.Order'
+ summary: Consulta pedido
+ tags:
+ - Pedidos
+ /api/orders/{id}/payment:
+ post:
+ parameters:
+ - description: Order ID
+ in: path
+ name: id
+ required: true
+ type: string
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/domain.PaymentPreference'
+ summary: Cria preferência de pagamento Mercado Pago com split nativo
+ tags:
+ - Pagamentos
+ /api/orders/{id}/status:
+ patch:
+ consumes:
+ - application/json
+ parameters:
+ - description: Order ID
+ in: path
+ name: id
+ required: true
+ type: string
+ - description: Novo status
+ in: body
+ name: status
+ required: true
+ schema:
+ $ref: '#/definitions/handler.updateStatusRequest'
+ produces:
+ - application/json
+ responses:
+ "204":
+ description: ""
+ summary: Atualiza status do pedido
+ tags:
+ - Pedidos
+ /api/products:
+ get:
+ produces:
+ - application/json
+ responses:
+ "200":
+ description: OK
+ schema:
+ items:
+ $ref: '#/definitions/domain.Product'
+ type: array
+ summary: Lista catálogo com lote e validade
+ tags:
+ - Produtos
+ post:
+ consumes:
+ - application/json
+ parameters:
+ - description: Produto
+ in: body
+ name: product
+ required: true
+ schema:
+ $ref: '#/definitions/handler.registerProductRequest'
+ produces:
+ - application/json
+ responses:
+ "201":
+ description: Created
+ schema:
+ $ref: '#/definitions/domain.Product'
+ summary: Cadastro de produto com rastreabilidade de lote
+ tags:
+ - Produtos
+schemes:
+- http
+swagger: "2.0"
diff --git a/backend-go/go.mod b/backend-go/go.mod
new file mode 100644
index 0000000..5674bb3
--- /dev/null
+++ b/backend-go/go.mod
@@ -0,0 +1,36 @@
+module github.com/saveinmed/backend-go
+
+go 1.24.3
+
+require (
+ github.com/gofrs/uuid/v5 v5.4.0
+ github.com/jackc/pgx/v5 v5.7.6
+ github.com/jmoiron/sqlx v1.4.0
+ github.com/json-iterator/go v1.1.12
+ github.com/swaggo/http-swagger v1.3.4
+ github.com/swaggo/swag v1.16.6
+)
+
+require (
+ github.com/KyleBanks/depth v1.2.1 // indirect
+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/spec v0.20.6 // indirect
+ github.com/go-openapi/swag v0.19.15 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/mailru/easyjson v0.7.6 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/rogpeppe/go-internal v1.14.1 // indirect
+ github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
+ golang.org/x/crypto v0.37.0 // indirect
+ golang.org/x/mod v0.21.0 // indirect
+ golang.org/x/net v0.34.0 // indirect
+ golang.org/x/sync v0.13.0 // indirect
+ golang.org/x/text v0.24.0 // indirect
+ golang.org/x/tools v0.26.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+)
diff --git a/backend-go/go.sum b/backend-go/go.sum
new file mode 100644
index 0000000..653c0df
--- /dev/null
+++ b/backend-go/go.sum
@@ -0,0 +1,103 @@
+filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
+filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
+github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
+github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
+github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
+github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
+github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
+github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
+github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
+github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
+github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
+github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
+github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
+github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
+golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
+golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
+golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
+golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/backend-go/internal/config/config.go b/backend-go/internal/config/config.go
new file mode 100644
index 0000000..d36bdf0
--- /dev/null
+++ b/backend-go/internal/config/config.go
@@ -0,0 +1,63 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "time"
+)
+
+// Config centralizes runtime configuration loaded from the environment.
+type Config struct {
+ AppName string
+ Port string
+ DatabaseURL string
+ MaxOpenConns int
+ MaxIdleConns int
+ ConnMaxIdle time.Duration
+}
+
+// Load reads configuration from environment variables and applies sane defaults
+// for local development.
+func Load() Config {
+ cfg := Config{
+ AppName: getEnv("APP_NAME", "saveinmed-performance-core"),
+ Port: getEnv("PORT", "8080"),
+ DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/saveinmed?sslmode=disable"),
+ MaxOpenConns: getEnvInt("DB_MAX_OPEN_CONNS", 15),
+ MaxIdleConns: getEnvInt("DB_MAX_IDLE_CONNS", 5),
+ ConnMaxIdle: getEnvDuration("DB_CONN_MAX_IDLE", 5*time.Minute),
+ }
+
+ return cfg
+}
+
+// Addr returns the address binding for the HTTP server.
+func (c Config) Addr() string {
+ return fmt.Sprintf(":%s", c.Port)
+}
+
+func getEnv(key, fallback string) string {
+ if value := os.Getenv(key); value != "" {
+ return value
+ }
+ return fallback
+}
+
+func getEnvInt(key string, fallback int) int {
+ if value := os.Getenv(key); value != "" {
+ if parsed, err := strconv.Atoi(value); err == nil {
+ return parsed
+ }
+ }
+ return fallback
+}
+
+func getEnvDuration(key string, fallback time.Duration) time.Duration {
+ if value := os.Getenv(key); value != "" {
+ if parsed, err := time.ParseDuration(value); err == nil {
+ return parsed
+ }
+ }
+ return fallback
+}
diff --git a/backend-go/internal/domain/models.go b/backend-go/internal/domain/models.go
new file mode 100644
index 0000000..ad48a5a
--- /dev/null
+++ b/backend-go/internal/domain/models.go
@@ -0,0 +1,75 @@
+package domain
+
+import (
+ "time"
+
+ "github.com/gofrs/uuid/v5"
+)
+
+// Company represents a B2B actor in the marketplace.
+type Company struct {
+ ID uuid.UUID `db:"id" json:"id"`
+ Role string `db:"role" json:"role"` // pharmacy, distributor, admin
+ CNPJ string `db:"cnpj" json:"cnpj"`
+ CorporateName string `db:"corporate_name" json:"corporate_name"`
+ SanitaryLicense string `db:"sanitary_license" json:"sanitary_license"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+}
+
+// Product represents a medicine SKU with batch tracking.
+type Product struct {
+ ID uuid.UUID `db:"id" json:"id"`
+ SellerID uuid.UUID `db:"seller_id" json:"seller_id"`
+ Name string `db:"name" json:"name"`
+ Description string `db:"description" json:"description"`
+ Batch string `db:"batch" json:"batch"`
+ ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
+ PriceCents int64 `db:"price_cents" json:"price_cents"`
+ Stock int64 `db:"stock" json:"stock"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+}
+
+// Order captures the status lifecycle and payment intent.
+type Order struct {
+ ID uuid.UUID `db:"id" json:"id"`
+ BuyerID uuid.UUID `db:"buyer_id" json:"buyer_id"`
+ SellerID uuid.UUID `db:"seller_id" json:"seller_id"`
+ Status OrderStatus `db:"status" json:"status"`
+ TotalCents int64 `db:"total_cents" json:"total_cents"`
+ Items []OrderItem `json:"items"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+}
+
+// OrderItem stores SKU-level batch tracking.
+type OrderItem struct {
+ ID uuid.UUID `db:"id" json:"id"`
+ OrderID uuid.UUID `db:"order_id" json:"order_id"`
+ ProductID uuid.UUID `db:"product_id" json:"product_id"`
+ Quantity int64 `db:"quantity" json:"quantity"`
+ UnitCents int64 `db:"unit_cents" json:"unit_cents"`
+ Batch string `db:"batch" json:"batch"`
+ ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
+}
+
+// PaymentPreference wraps the data needed for Mercado Pago split payments.
+type PaymentPreference struct {
+ OrderID uuid.UUID `json:"order_id"`
+ Gateway string `json:"gateway"`
+ CommissionPct float64 `json:"commission_pct"`
+ MarketplaceFee int64 `json:"marketplace_fee"`
+ SellerReceivable int64 `json:"seller_receivable"`
+ PaymentURL string `json:"payment_url"`
+}
+
+// OrderStatus enumerates supported transitions.
+type OrderStatus string
+
+const (
+ OrderStatusPending OrderStatus = "Pendente"
+ OrderStatusPaid OrderStatus = "Pago"
+ OrderStatusInvoiced OrderStatus = "Faturado"
+ OrderStatusDelivered OrderStatus = "Entregue"
+)
diff --git a/backend-go/internal/http/handler/handler.go b/backend-go/internal/http/handler/handler.go
new file mode 100644
index 0000000..aaa8c7d
--- /dev/null
+++ b/backend-go/internal/http/handler/handler.go
@@ -0,0 +1,326 @@
+package handler
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ jsoniter "github.com/json-iterator/go"
+
+ "github.com/gofrs/uuid/v5"
+
+ "github.com/saveinmed/backend-go/internal/domain"
+ "github.com/saveinmed/backend-go/internal/usecase"
+)
+
+var json = jsoniter.ConfigCompatibleWithStandardLibrary
+
+type Handler struct {
+ svc *usecase.Service
+}
+
+func New(svc *usecase.Service) *Handler {
+ return &Handler{svc: svc}
+}
+
+// CreateCompany godoc
+// @Summary Registro de empresas
+// @Description Cadastra farmácia, distribuidora ou administrador com CNPJ e licença sanitária.
+// @Tags Empresas
+// @Accept json
+// @Produce json
+// @Param company body registerCompanyRequest true "Dados da empresa"
+// @Success 201 {object} domain.Company
+// @Router /api/companies [post]
+func (h *Handler) CreateCompany(w http.ResponseWriter, r *http.Request) {
+ var req registerCompanyRequest
+ if err := decodeJSON(r.Context(), r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ company := &domain.Company{
+ Role: req.Role,
+ CNPJ: req.CNPJ,
+ CorporateName: req.CorporateName,
+ SanitaryLicense: req.SanitaryLicense,
+ }
+
+ if err := h.svc.RegisterCompany(r.Context(), company); err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusCreated, company)
+}
+
+// ListCompanies godoc
+// @Summary Lista empresas
+// @Tags Empresas
+// @Produce json
+// @Success 200 {array} domain.Company
+// @Router /api/companies [get]
+func (h *Handler) ListCompanies(w http.ResponseWriter, r *http.Request) {
+ companies, err := h.svc.ListCompanies(r.Context())
+ if err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, companies)
+}
+
+// CreateProduct godoc
+// @Summary Cadastro de produto com rastreabilidade de lote
+// @Tags Produtos
+// @Accept json
+// @Produce json
+// @Param product body registerProductRequest true "Produto"
+// @Success 201 {object} domain.Product
+// @Router /api/products [post]
+func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) {
+ var req registerProductRequest
+ if err := decodeJSON(r.Context(), r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ product := &domain.Product{
+ SellerID: req.SellerID,
+ Name: req.Name,
+ Description: req.Description,
+ Batch: req.Batch,
+ ExpiresAt: req.ExpiresAt,
+ PriceCents: req.PriceCents,
+ Stock: req.Stock,
+ }
+
+ if err := h.svc.RegisterProduct(r.Context(), product); err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusCreated, product)
+}
+
+// ListProducts godoc
+// @Summary Lista catálogo com lote e validade
+// @Tags Produtos
+// @Produce json
+// @Success 200 {array} domain.Product
+// @Router /api/products [get]
+func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) {
+ products, err := h.svc.ListProducts(r.Context())
+ if err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+ writeJSON(w, http.StatusOK, products)
+}
+
+// CreateOrder godoc
+// @Summary Criação de pedido com split
+// @Tags Pedidos
+// @Accept json
+// @Produce json
+// @Param order body createOrderRequest true "Pedido"
+// @Success 201 {object} domain.Order
+// @Router /api/orders [post]
+func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
+ var req createOrderRequest
+ if err := decodeJSON(r.Context(), r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ order := &domain.Order{
+ BuyerID: req.BuyerID,
+ SellerID: req.SellerID,
+ Items: req.Items,
+ }
+
+ var total int64
+ for _, item := range req.Items {
+ total += item.UnitCents * item.Quantity
+ }
+ order.TotalCents = total
+
+ if err := h.svc.CreateOrder(r.Context(), order); err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusCreated, order)
+}
+
+// GetOrder godoc
+// @Summary Consulta pedido
+// @Tags Pedidos
+// @Produce json
+// @Param id path string true "Order ID"
+// @Success 200 {object} domain.Order
+// @Router /api/orders/{id} [get]
+func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
+ id, err := parseUUIDFromPath(r.URL.Path)
+ if err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ order, err := h.svc.GetOrder(r.Context(), id)
+ if err != nil {
+ writeError(w, http.StatusNotFound, err)
+ return
+ }
+
+ writeJSON(w, http.StatusOK, order)
+}
+
+// UpdateOrderStatus godoc
+// @Summary Atualiza status do pedido
+// @Tags Pedidos
+// @Accept json
+// @Produce json
+// @Param id path string true "Order ID"
+// @Param status body updateStatusRequest true "Novo status"
+// @Success 204 ""
+// @Router /api/orders/{id}/status [patch]
+func (h *Handler) UpdateOrderStatus(w http.ResponseWriter, r *http.Request) {
+ id, err := parseUUIDFromPath(r.URL.Path)
+ if err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ var req updateStatusRequest
+ if err := decodeJSON(r.Context(), r, &req); err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ if !isValidStatus(req.Status) {
+ writeError(w, http.StatusBadRequest, errors.New("invalid status"))
+ return
+ }
+
+ if err := h.svc.UpdateOrderStatus(r.Context(), id, domain.OrderStatus(req.Status)); err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// CreatePaymentPreference godoc
+// @Summary Cria preferência de pagamento Mercado Pago com split nativo
+// @Tags Pagamentos
+// @Produce json
+// @Param id path string true "Order ID"
+// @Success 201 {object} domain.PaymentPreference
+// @Router /api/orders/{id}/payment [post]
+func (h *Handler) CreatePaymentPreference(w http.ResponseWriter, r *http.Request) {
+ if !strings.HasSuffix(r.URL.Path, "/payment") {
+ http.NotFound(w, r)
+ return
+ }
+
+ id, err := parseUUIDFromPath(r.URL.Path)
+ if err != nil {
+ writeError(w, http.StatusBadRequest, err)
+ return
+ }
+
+ pref, err := h.svc.CreatePaymentPreference(r.Context(), id)
+ if err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusCreated, pref)
+}
+
+type registerCompanyRequest struct {
+ Role string `json:"role"`
+ CNPJ string `json:"cnpj"`
+ CorporateName string `json:"corporate_name"`
+ SanitaryLicense string `json:"sanitary_license"`
+}
+
+type registerProductRequest struct {
+ SellerID uuid.UUID `json:"seller_id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Batch string `json:"batch"`
+ ExpiresAt time.Time `json:"expires_at"`
+ PriceCents int64 `json:"price_cents"`
+ Stock int64 `json:"stock"`
+}
+
+type createOrderRequest struct {
+ BuyerID uuid.UUID `json:"buyer_id"`
+ SellerID uuid.UUID `json:"seller_id"`
+ Items []domain.OrderItem `json:"items"`
+}
+
+type updateStatusRequest struct {
+ Status string `json:"status"`
+}
+
+func writeJSON(w http.ResponseWriter, status int, v any) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ _ = json.NewEncoder(w).Encode(v)
+}
+
+func writeError(w http.ResponseWriter, status int, err error) {
+ writeJSON(w, status, map[string]string{"error": err.Error()})
+}
+
+func decodeJSON(ctx context.Context, r *http.Request, v any) error {
+ ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
+ defer cancel()
+
+ dec := json.NewDecoder(r.Body)
+ dec.DisallowUnknownFields()
+ if err := dec.Decode(v); err != nil {
+ return err
+ }
+ return ctx.Err()
+}
+
+func parseUUIDFromPath(path string) (uuid.UUID, error) {
+ parts := splitPath(path)
+ for i := len(parts) - 1; i >= 0; i-- {
+ if id, err := uuid.FromString(parts[i]); err == nil {
+ return id, nil
+ }
+ }
+ return uuid.UUID{}, errors.New("missing resource id")
+}
+
+func splitPath(p string) []string {
+ var parts []string
+ start := 0
+ for i := 0; i < len(p); i++ {
+ if p[i] == '/' {
+ if i > start {
+ parts = append(parts, p[start:i])
+ }
+ start = i + 1
+ }
+ }
+ if start < len(p) {
+ parts = append(parts, p[start:])
+ }
+ return parts
+}
+
+func isValidStatus(status string) bool {
+ switch domain.OrderStatus(status) {
+ case domain.OrderStatusPending, domain.OrderStatusPaid, domain.OrderStatusInvoiced, domain.OrderStatusDelivered:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/backend-go/internal/http/middleware/compress.go b/backend-go/internal/http/middleware/compress.go
new file mode 100644
index 0000000..397daaf
--- /dev/null
+++ b/backend-go/internal/http/middleware/compress.go
@@ -0,0 +1,36 @@
+package middleware
+
+import (
+ "compress/gzip"
+ "io"
+ "net/http"
+ "strings"
+)
+
+// Gzip wraps HTTP responses with gzip compression when supported by the client.
+func Gzip(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ gz := gzip.NewWriter(w)
+ defer gz.Close()
+
+ w.Header().Set("Content-Encoding", "gzip")
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
+ next.ServeHTTP(gzr, r)
+ })
+}
+
+type gzipResponseWriter struct {
+ io.Writer
+ http.ResponseWriter
+}
+
+func (w gzipResponseWriter) Write(b []byte) (int, error) {
+ return w.Writer.Write(b)
+}
diff --git a/backend-go/internal/http/middleware/logging.go b/backend-go/internal/http/middleware/logging.go
new file mode 100644
index 0000000..ce77bb0
--- /dev/null
+++ b/backend-go/internal/http/middleware/logging.go
@@ -0,0 +1,16 @@
+package middleware
+
+import (
+ "log"
+ "net/http"
+ "time"
+)
+
+// Logger records basic request information.
+func Logger(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ start := time.Now()
+ next.ServeHTTP(w, r)
+ log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
+ })
+}
diff --git a/backend-go/internal/payments/mercadopago.go b/backend-go/internal/payments/mercadopago.go
new file mode 100644
index 0000000..59a0795
--- /dev/null
+++ b/backend-go/internal/payments/mercadopago.go
@@ -0,0 +1,44 @@
+package payments
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/saveinmed/backend-go/internal/domain"
+)
+
+// MercadoPagoGateway fakes the split configuration while keeping the API contract ready for the SDK.
+type MercadoPagoGateway struct {
+ MarketplaceCommission float64
+ BaseURL string
+}
+
+func NewMercadoPagoGateway() *MercadoPagoGateway {
+ return &MercadoPagoGateway{
+ MarketplaceCommission: 2.5,
+ BaseURL: "https://api.mercadopago.com",
+ }
+}
+
+func (g *MercadoPagoGateway) CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error) {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ fee := int64(float64(order.TotalCents) * (g.MarketplaceCommission / 100))
+ pref := &domain.PaymentPreference{
+ OrderID: order.ID,
+ Gateway: "mercadopago",
+ CommissionPct: g.MarketplaceCommission,
+ MarketplaceFee: fee,
+ SellerReceivable: order.TotalCents - fee,
+ PaymentURL: fmt.Sprintf("%s/checkout/v1/redirect?order_id=%s", g.BaseURL, order.ID.String()),
+ }
+
+ // In a real gateway this is where the SDK call would run; we preserve latency budgets.
+ time.Sleep(10 * time.Millisecond)
+ return pref, nil
+}
diff --git a/backend-go/internal/repository/postgres/postgres.go b/backend-go/internal/repository/postgres/postgres.go
new file mode 100644
index 0000000..71b0242
--- /dev/null
+++ b/backend-go/internal/repository/postgres/postgres.go
@@ -0,0 +1,181 @@
+package postgres
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/gofrs/uuid/v5"
+ "github.com/jmoiron/sqlx"
+
+ "github.com/saveinmed/backend-go/internal/domain"
+)
+
+// Repository implements the data access layer using sqlx + pgx.
+type Repository struct {
+ db *sqlx.DB
+}
+
+// New creates a Postgres-backed repository and configures pooling.
+func New(db *sqlx.DB) *Repository {
+ return &Repository{db: db}
+}
+
+func (r *Repository) CreateCompany(ctx context.Context, company *domain.Company) error {
+ now := time.Now().UTC()
+ company.CreatedAt = now
+ company.UpdatedAt = now
+
+ query := `INSERT INTO companies (id, role, cnpj, corporate_name, sanitary_license, created_at, updated_at)
+VALUES (:id, :role, :cnpj, :corporate_name, :sanitary_license, :created_at, :updated_at)`
+
+ _, err := r.db.NamedExecContext(ctx, query, company)
+ return err
+}
+
+func (r *Repository) ListCompanies(ctx context.Context) ([]domain.Company, error) {
+ var companies []domain.Company
+ query := `SELECT id, role, cnpj, corporate_name, sanitary_license, created_at, updated_at FROM companies ORDER BY created_at DESC`
+ if err := r.db.SelectContext(ctx, &companies, query); err != nil {
+ return nil, err
+ }
+ return companies, nil
+}
+
+func (r *Repository) CreateProduct(ctx context.Context, product *domain.Product) error {
+ now := time.Now().UTC()
+ product.CreatedAt = now
+ product.UpdatedAt = now
+
+ query := `INSERT INTO products (id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at)
+VALUES (:id, :seller_id, :name, :description, :batch, :expires_at, :price_cents, :stock, :created_at, :updated_at)`
+
+ _, err := r.db.NamedExecContext(ctx, query, product)
+ return err
+}
+
+func (r *Repository) ListProducts(ctx context.Context) ([]domain.Product, error) {
+ var products []domain.Product
+ query := `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products ORDER BY created_at DESC`
+ if err := r.db.SelectContext(ctx, &products, query); err != nil {
+ return nil, err
+ }
+ return products, nil
+}
+
+func (r *Repository) CreateOrder(ctx context.Context, order *domain.Order) error {
+ now := time.Now().UTC()
+ order.CreatedAt = now
+ order.UpdatedAt = now
+
+ tx, err := r.db.BeginTxx(ctx, nil)
+ if err != nil {
+ return err
+ }
+
+ orderQuery := `INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, created_at, updated_at)
+VALUES ($1, $2, $3, $4, $5, $6, $7)`
+ if _, err := tx.ExecContext(ctx, orderQuery, order.ID, order.BuyerID, order.SellerID, order.Status, order.TotalCents, order.CreatedAt, order.UpdatedAt); err != nil {
+ _ = tx.Rollback()
+ return err
+ }
+
+ itemQuery := `INSERT INTO order_items (id, order_id, product_id, quantity, unit_cents, batch, expires_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`
+ for i := range order.Items {
+ item := &order.Items[i]
+ item.ID = uuid.Must(uuid.NewV7())
+ item.OrderID = order.ID
+ if _, err := tx.ExecContext(ctx, itemQuery, item.ID, item.OrderID, item.ProductID, item.Quantity, item.UnitCents, item.Batch, item.ExpiresAt); err != nil {
+ _ = tx.Rollback()
+ return err
+ }
+ }
+
+ return tx.Commit()
+}
+
+func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
+ var order domain.Order
+ orderQuery := `SELECT id, buyer_id, seller_id, status, total_cents, created_at, updated_at FROM orders WHERE id = $1`
+ if err := r.db.GetContext(ctx, &order, orderQuery, id); err != nil {
+ return nil, err
+ }
+
+ var items []domain.OrderItem
+ itemQuery := `SELECT id, order_id, product_id, quantity, unit_cents, batch, expires_at FROM order_items WHERE order_id = $1`
+ if err := r.db.SelectContext(ctx, &items, itemQuery, id); err != nil {
+ return nil, err
+ }
+ order.Items = items
+ return &order, nil
+}
+
+func (r *Repository) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
+ query := `UPDATE orders SET status = $1, updated_at = $2 WHERE id = $3`
+ res, err := r.db.ExecContext(ctx, query, status, time.Now().UTC(), id)
+ if err != nil {
+ return err
+ }
+ rows, err := res.RowsAffected()
+ if err != nil {
+ return err
+ }
+ if rows == 0 {
+ return errors.New("order not found")
+ }
+ return nil
+}
+
+// InitSchema applies a minimal schema for development environments.
+func (r *Repository) InitSchema(ctx context.Context) error {
+ schema := `
+CREATE TABLE IF NOT EXISTS companies (
+ id UUID PRIMARY KEY,
+ role TEXT NOT NULL,
+ cnpj TEXT NOT NULL UNIQUE,
+ corporate_name TEXT NOT NULL,
+ sanitary_license TEXT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL,
+ updated_at TIMESTAMPTZ NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS products (
+ id UUID PRIMARY KEY,
+ seller_id UUID NOT NULL REFERENCES companies(id),
+ name TEXT NOT NULL,
+ description TEXT,
+ batch TEXT NOT NULL,
+ expires_at DATE NOT NULL,
+ price_cents BIGINT NOT NULL,
+ stock BIGINT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL,
+ updated_at TIMESTAMPTZ NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS orders (
+ id UUID PRIMARY KEY,
+ buyer_id UUID NOT NULL REFERENCES companies(id),
+ seller_id UUID NOT NULL REFERENCES companies(id),
+ status TEXT NOT NULL,
+ total_cents BIGINT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL,
+ updated_at TIMESTAMPTZ NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS order_items (
+ id UUID PRIMARY KEY,
+ order_id UUID NOT NULL REFERENCES orders(id),
+ product_id UUID NOT NULL REFERENCES products(id),
+ quantity BIGINT NOT NULL,
+ unit_cents BIGINT NOT NULL,
+ batch TEXT NOT NULL,
+ expires_at DATE NOT NULL
+);
+`
+
+ if _, err := r.db.ExecContext(ctx, schema); err != nil {
+ return fmt.Errorf("apply schema: %w", err)
+ }
+ return nil
+}
diff --git a/backend-go/internal/server/server.go b/backend-go/internal/server/server.go
new file mode 100644
index 0000000..7fc5049
--- /dev/null
+++ b/backend-go/internal/server/server.go
@@ -0,0 +1,92 @@
+package server
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "time"
+
+ _ "github.com/jackc/pgx/v5/stdlib"
+ "github.com/jmoiron/sqlx"
+ httpSwagger "github.com/swaggo/http-swagger"
+
+ "github.com/saveinmed/backend-go/internal/config"
+ "github.com/saveinmed/backend-go/internal/http/handler"
+ "github.com/saveinmed/backend-go/internal/http/middleware"
+ "github.com/saveinmed/backend-go/internal/payments"
+ "github.com/saveinmed/backend-go/internal/repository/postgres"
+ "github.com/saveinmed/backend-go/internal/usecase"
+)
+
+// Server wires the infrastructure and exposes HTTP handlers.
+type Server struct {
+ cfg config.Config
+ db *sqlx.DB
+ mux *http.ServeMux
+}
+
+func New(cfg config.Config) (*Server, error) {
+ db, err := sqlx.Open("pgx", cfg.DatabaseURL)
+ if err != nil {
+ return nil, err
+ }
+
+ db.SetMaxOpenConns(cfg.MaxOpenConns)
+ db.SetMaxIdleConns(cfg.MaxIdleConns)
+ db.SetConnMaxIdleTime(cfg.ConnMaxIdle)
+
+ repo := postgres.New(db)
+ gateway := payments.NewMercadoPagoGateway()
+ svc := usecase.NewService(repo, gateway)
+ h := handler.New(svc)
+
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("ok"))
+ })
+
+ 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("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("POST /api/orders", chain(http.HandlerFunc(h.CreateOrder), middleware.Logger, middleware.Gzip))
+ mux.Handle("GET /api/orders/", chain(http.HandlerFunc(h.GetOrder), middleware.Logger, middleware.Gzip))
+ mux.Handle("PATCH /api/orders/", chain(http.HandlerFunc(h.UpdateOrderStatus), middleware.Logger, middleware.Gzip))
+ mux.Handle("POST /api/orders/", chain(http.HandlerFunc(h.CreatePaymentPreference), middleware.Logger, middleware.Gzip))
+
+ mux.Handle("GET /swagger/", httpSwagger.Handler(httpSwagger.URL("/swagger/doc.json")))
+
+ return &Server{cfg: cfg, db: db, mux: mux}, nil
+}
+
+// Start runs the HTTP server and ensures the database is reachable.
+func (s *Server) Start(ctx context.Context) error {
+ if err := s.db.PingContext(ctx); err != nil {
+ return err
+ }
+
+ repo := postgres.New(s.db)
+ if err := repo.InitSchema(ctx); err != nil {
+ return err
+ }
+
+ srv := &http.Server{
+ Addr: s.cfg.Addr(),
+ Handler: s.mux,
+ ReadHeaderTimeout: 5 * time.Second,
+ }
+
+ log.Printf("starting %s on %s", s.cfg.AppName, s.cfg.Addr())
+ return srv.ListenAndServe()
+}
+
+func chain(h http.Handler, middlewareFns ...func(http.Handler) http.Handler) http.Handler {
+ for i := len(middlewareFns) - 1; i >= 0; i-- {
+ h = middlewareFns[i](h)
+ }
+ return h
+}
diff --git a/backend-go/internal/usecase/usecase.go b/backend-go/internal/usecase/usecase.go
new file mode 100644
index 0000000..2a3d0e0
--- /dev/null
+++ b/backend-go/internal/usecase/usecase.go
@@ -0,0 +1,77 @@
+package usecase
+
+import (
+ "context"
+
+ "github.com/gofrs/uuid/v5"
+
+ "github.com/saveinmed/backend-go/internal/domain"
+)
+
+// Repository defines DB contract for the core entities.
+type Repository interface {
+ CreateCompany(ctx context.Context, company *domain.Company) error
+ ListCompanies(ctx context.Context) ([]domain.Company, error)
+
+ CreateProduct(ctx context.Context, product *domain.Product) error
+ ListProducts(ctx context.Context) ([]domain.Product, error)
+
+ CreateOrder(ctx context.Context, order *domain.Order) error
+ GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error)
+ UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error
+}
+
+// PaymentGateway abstracts Mercado Pago integration.
+type PaymentGateway interface {
+ CreatePreference(ctx context.Context, order *domain.Order) (*domain.PaymentPreference, error)
+}
+
+type Service struct {
+ repo Repository
+ pay PaymentGateway
+}
+
+// NewService wires use cases together.
+func NewService(repo Repository, pay PaymentGateway) *Service {
+ return &Service{repo: repo, pay: pay}
+}
+
+func (s *Service) RegisterCompany(ctx context.Context, company *domain.Company) error {
+ company.ID = uuid.Must(uuid.NewV7())
+ return s.repo.CreateCompany(ctx, company)
+}
+
+func (s *Service) ListCompanies(ctx context.Context) ([]domain.Company, error) {
+ return s.repo.ListCompanies(ctx)
+}
+
+func (s *Service) RegisterProduct(ctx context.Context, product *domain.Product) error {
+ product.ID = uuid.Must(uuid.NewV7())
+ return s.repo.CreateProduct(ctx, product)
+}
+
+func (s *Service) ListProducts(ctx context.Context) ([]domain.Product, error) {
+ return s.repo.ListProducts(ctx)
+}
+
+func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error {
+ order.ID = uuid.Must(uuid.NewV7())
+ order.Status = domain.OrderStatusPending
+ return s.repo.CreateOrder(ctx, order)
+}
+
+func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
+ return s.repo.GetOrder(ctx, id)
+}
+
+func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error {
+ return s.repo.UpdateOrderStatus(ctx, id, status)
+}
+
+func (s *Service) CreatePaymentPreference(ctx context.Context, id uuid.UUID) (*domain.PaymentPreference, error) {
+ order, err := s.repo.GetOrder(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+ return s.pay.CreatePreference(ctx, order)
+}
diff --git a/website/routes/index.tsx b/website/routes/index.tsx
index b15b1f2..1e3130d 100644
--- a/website/routes/index.tsx
+++ b/website/routes/index.tsx
@@ -1,30 +1,90 @@
-import { useSignal } from "@preact/signals";
import { define } from "../utils.ts";
-import Counter from "../islands/Counter.tsx";
+
+const highlights = [
+ {
+ title: "Hierarquia de Permissões",
+ description:
+ "Perfis separados para Farmácia (comprador), Distribuidora (vendedor) e Administrador do marketplace com trilhas dedicadas.",
+ },
+ {
+ title: "Rastreabilidade de Lote",
+ description:
+ "Produtos carregam lote e validade obrigatórios em todo pedido, garantindo conformidade sanitária de ponta a ponta.",
+ },
+ {
+ title: "Split nativo Mercado Pago",
+ description:
+ "Preferências de pagamento prontas para retenção automática de comissão do marketplace e repasse ao seller.",
+ },
+ {
+ title: "Impostos por UF",
+ description:
+ "Camada de cálculo preparada para tabelas de substituição tributária estadual, simplificando faturamento fiscal.",
+ },
+];
export default define.page(function Home(ctx) {
- const count = useSignal(3);
-
- ctx.state.title = count.value + " Fresh Counter" +
- (Math.abs(count.value) === 1 ? "" : "s");
+ ctx.state.title = "SaveInMed | Marketplace B2B";
return (
-
-
-

-
Welcome to Fresh
-
- Try updating this message in the
- ./routes/index.tsx file, and refresh.
-
-
-
+
+
+
+
+
+ Planejamento B2B Farmacêutico
+
+
+ Nova fundação de Performance para o Marketplace SaveInMed
+
+
+ Arquitetura renovada com backend em Go ultrarrápido, Postgres 17 e Clean Architecture.
+ Ideal para transações de alto volume e integração nativa com Mercado Pago.
+
+
+
+
Core em Go 1.24+
+
net/http, pgx, json-iter, gzip
+
+
+
Infra pronta para produção
+
Docker distroless + Swagger
+
+
+
+
+
Stack de diretórios
+
+ - /backend-go: Performance Core (Pagamentos)
+ - /backend-nest: Gestão de usuários, CRM e regras de negócio
+ - /frontend-market: App da farmácia em React + Vite
+ - /website: Landing page institucional
+ - /docker: Compose com Postgres e backends
+
+
+
+
+
+
+
+
+ {highlights.map((item) => (
+
+
{item.title}
+
{item.description}
+
+ ))}
+
+
+
+ Foco em Observabilidade e Resiliência
+
+ Conexões pgx otimizadas, middlewares de compressão e roteamento do Go 1.22+ garantem baixa latência.
+ O repositório vem com schema base e UUID v7 ordenável para índices mais eficientes.
+
+
+
+
);
});