Exemplo Go

Este documento contém um exemplo completo de implementação da API Flowbiz em Go.


Código Completo

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "time"
)

// Configuração
const (
	API_KEY = "sua_api_key_aqui" // Em produção, use variáveis de ambiente
	PORT    = 8080
)

// Estruturas de dados (JSON camelCase conforme manual)
type Brand struct {
    Id   string `json:"id"`
    Name string `json:"name"`
}

type ProductCategory struct {
    Id   string `json:"id"`
    Name string `json:"name"`
}

type Variant struct {
    Sku       string  `json:"sku"`
    Name      string  `json:"name"`
    Price     float64 `json:"price"`
    Stock     int     `json:"stock"`
    ImageUrl  string  `json:"imageUrl,omitempty"`
    Url       string  `json:"url,omitempty"`
    Available bool    `json:"available"`
}

type Product struct {
    ProductId string            `json:"productId"`
    Url       string            `json:"url"`
    Brand     Brand             `json:"brand"`
    Category  []ProductCategory `json:"category"`
    Variants  []Variant         `json:"variants"`
}

type OrderItem struct {
    ProductId  string            `json:"productId"`
    Categories []ProductCategory `json:"categories"`
    Sku        string            `json:"sku"`
    Name       string            `json:"name"`
    Brand      string            `json:"brand"`
    Price      float64           `json:"price"`
    Quantity   int               `json:"quantity"`
    Url        string            `json:"url,omitempty"`
    ImageUrl   string            `json:"imageUrl,omitempty"`
}

type PaymentMethod struct {
    Type   string  `json:"type"`
    Amount float64 `json:"amount"`
}

type DeliveryMethod struct {
    Type   string  `json:"type"`
    Amount float64 `json:"amount"`
}

type DeliveryAddress struct {
    City          string `json:"city"`
    AddressLine2  string `json:"addressLine2,omitempty"`
    Neighborhood  string `json:"neighborhood,omitempty"`
    AddressNumber string `json:"addressNumber,omitempty"`
    State         string `json:"state"`
    AddressLine1  string `json:"addressLine1"`
    PostalCode    string `json:"postalCode"`
    Country       string `json:"country,omitempty"`
}

type Order struct {
    Platform        string           `json:"platform"`
    OrderId         string           `json:"orderId"`
    Date            time.Time        `json:"date"`
    Subtotal        float64          `json:"subtotal"`
    Freight         float64          `json:"freight"`
    Discounts       float64          `json:"discounts"`
    Total           float64          `json:"total"`
    Currency        string           `json:"currency"`
    RawPaymentStatus string          `json:"rawPaymentStatus"`
    IsPaid          bool             `json:"isPaid"`
    CustomerId      string           `json:"customerId"`
    CustomerEmail   string           `json:"customerEmail"`
    Items           []OrderItem      `json:"items"`
    PaymentMethods  []PaymentMethod  `json:"paymentMethods,omitempty"`
    DeliveryMethods []DeliveryMethod `json:"deliveryMethods,omitempty"`
    DeliveryAddress DeliveryAddress  `json:"deliveryAddress,omitempty"`
}

// Middleware para validar a API Key
func validateAPIKey(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		apiKey := r.Header.Get("X-Impulse-Key")

  if apiKey != API_KEY {
            w.WriteHeader(http.StatusUnauthorized)
            json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized", "message": "X-Impulse-Key is missing or invalid"})
            return
        }

		next.ServeHTTP(w, r)
	})
}

// Função auxiliar para obter parâmetros de paginação
func getPaginationParams(r *http.Request) (int, int) {
    offset := 0
    limit := 50

    if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
        if val, err := strconv.Atoi(offsetStr); err == nil {
            offset = val
        }
    }

    if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
        if val, err := strconv.Atoi(limitStr); err == nil {
            limit = val
        }
    }

    if limit <= 0 {
        limit = 50
    }
    if limit > 100 {
        limit = 100
    }
    if offset < 0 {
        offset = 0
    }

    return offset, limit
}

// Função auxiliar para obter e validar filtro de data (YYYY-MM-DD)
func getDateFilter(r *http.Request) (time.Time, time.Time, bool) {
    start := r.URL.Query().Get("start_date")
    end := r.URL.Query().Get("end_date")
    if start == "" || end == "" {
        return time.Time{}, time.Time{}, false
    }
    // Inclusivo: dia inteiro
    startDate, err1 := time.Parse(time.RFC3339, start+"T00:00:00Z")
    endDate, err2 := time.Parse(time.RFC3339, end+"T23:59:59Z")
    if err1 != nil || err2 != nil {
        return time.Time{}, time.Time{}, false
    }
    return startDate, endDate, true
}

// Função para responder com envelope de paginação
func respondPaginated(w http.ResponseWriter, items interface{}, total, offset, limit, count int) {
    hasNext := (offset + count) < total
    resp := map[string]interface{}{
        "data":     items,
        "total":    total,
        "offset":   offset,
        "limit":    limit,
        "count":    count,
        "hasNext":  hasNext,
    }
    if hasNext {
        resp["nextOffset"] = offset + count
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

// Handlers para os endpoints
func categoriesHandler(w http.ResponseWriter, r *http.Request) {
    offset, limit := getPaginationParams(r)

    all := []ProductCategory{
        {Id: "1", Name: "Eletrônicos"},
        {Id: "2", Name: "Roupas"},
        {Id: "3", Name: "Acessórios"},
    }
    total := len(all)
    end := offset + limit
    if end > total {
        end = total
    }
    var page []ProductCategory
    if offset >= total {
        page = []ProductCategory{}
    } else {
        page = all[offset:end]
    }
    respondPaginated(w, page, total, offset, limit, len(page))
}

func brandsHandler(w http.ResponseWriter, r *http.Request) {
	offset, limit := getPaginationParams(r)

    all := []Brand{
        {Id: "1", Name: "Samsung"},
        {Id: "2", Name: "Apple"},
        {Id: "3", Name: "Nike"},
    }
    total := len(all)
    end := offset + limit
    if end > total {
        end = total
    }
    var page []Brand
    if offset >= total {
        page = []Brand{}
    } else {
        page = all[offset:end]
    }
    respondPaginated(w, page, total, offset, limit, len(page))
}

func productsHandler(w http.ResponseWriter, r *http.Request) {
	offset, limit := getPaginationParams(r)

    products := []Product{
        {
            ProductId: "1",
            Url:       "https://exemplo.com/produto/1",
            Brand:     Brand{Id: "1", Name: "Samsung"},
            Category:  []ProductCategory{{Id: "1", Name: "Eletrônicos"}},
            Variants:  []Variant{{Sku: "SKU-1A", Name: "Variante A", Price: 50.0, Stock: 10, Available: true}},
        },
        {
            ProductId: "2",
            Url:       "https://exemplo.com/produto/2",
            Brand:     Brand{Id: "2", Name: "Apple"},
            Category:  []ProductCategory{{Id: "1", Name: "Eletrônicos"}},
            Variants:  []Variant{{Sku: "SKU-2A", Name: "Variante A", Price: 75.0, Stock: 5, Available: true}},
        },
    }

    total := len(products)
    end := offset + limit
    if end > total {
        end = total
    }
    var page []Product
    if offset >= total {
        page = []Product{}
    } else {
        page = products[offset:end]
    }
    respondPaginated(w, page, total, offset, limit, len(page))
}

func ordersHandler(w http.ResponseWriter, r *http.Request) {
    offset, limit := getPaginationParams(r)
    startDate, endDate, hasDate := getDateFilter(r)

    // Simulação de dados
    all := []Order{
        {
            Platform:  "SuaPlataforma",
            OrderId:   "12345",
            Date:      time.Date(2023, 6, 1, 10, 0, 0, 0, time.UTC),
            Subtotal:  100.0,
            Freight:   10.0,
            Discounts: 5.0,
            Total:     105.0,
            Currency:  "BRL",
            RawPaymentStatus: "Pago",
            IsPaid:    true,
            CustomerId:    "1",
            CustomerEmail: "[email protected]",
            Items: []OrderItem{
                {
                    ProductId:  "1",
                    Categories: []ProductCategory{{Id: "1", Name: "Eletrônicos"}},
                    Sku:        "SKU-1A",
                    Name:       "Produto 1",
                    Brand:      "Samsung",
                    Price:      50.0,
                    Quantity:   2,
                    Url:        "https://exemplo.com/produto/1",
                    ImageUrl:   "https://exemplo.com/img/1.jpg",
                },
            },
            PaymentMethods:  []PaymentMethod{{Type: "credit_card", Amount: 105.0}},
            DeliveryMethods: []DeliveryMethod{{Type: "standard", Amount: 10.0}},
            DeliveryAddress: DeliveryAddress{
                City:         "São Paulo",
                AddressLine2: "Apto 101",
                Neighborhood: "Centro",
                AddressNumber: "123",
                State:        "SP",
                AddressLine1: "Rua Principal",
                PostalCode:   "01000-000",
                Country:      "BR",
            },
        },
    }

    // Validar filtro obrigatório
    if !hasDate {
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(map[string]string{"error": "bad_request", "message": "start_date and end_date are required in YYYY-MM-DD format"})
        return
    }

    // Filtrar por intervalo inclusivo
    filtered := make([]Order, 0)
    for _, o := range all {
        if (o.Date.Equal(startDate) || o.Date.After(startDate)) && (o.Date.Equal(endDate) || o.Date.Before(endDate)) {
            filtered = append(filtered, o)
        }
    }

    total := len(filtered)
    end := offset + limit
    if end > total {
        end = total
    }
    var page []Order
    if offset >= total {
        page = []Order{}
    } else {
        page = filtered[offset:end]
    }
    respondPaginated(w, page, total, offset, limit, len(page))
}

func main() {
    // Criar router
    mux := http.NewServeMux()

    // Aplicar middleware de validação da API Key
    apiKeyMiddleware := validateAPIKey

    // Registrar handlers
    mux.HandleFunc("/api/v2/healthz", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })
    mux.Handle("/api/v2/categories", apiKeyMiddleware(http.HandlerFunc(categoriesHandler)))
    mux.Handle("/api/v2/brands", apiKeyMiddleware(http.HandlerFunc(brandsHandler)))
    mux.Handle("/api/v2/products", apiKeyMiddleware(http.HandlerFunc(productsHandler)))
    mux.Handle("/api/v2/orders", apiKeyMiddleware(http.HandlerFunc(ordersHandler)))

	// Iniciar servidor
	serverAddr := fmt.Sprintf(":%d", PORT)
	log.Printf("Servidor iniciado na porta %d\n", PORT)
	log.Fatal(http.ListenAndServe(serverAddr, mux))
}

Explicação

Este exemplo implementa uma API REST em Go que atende aos requisitos do padrão Flowbiz. A implementação inclui:

  1. Estruturas de dados para representar os objetos do domínio (Order, Product, etc.)
  2. Middleware de autenticação para validar a API Key
  3. Endpoints para listar categorias, marcas, produtos e pedidos
  4. Suporte à paginação em todos os endpoints
  5. Filtro de data no endpoint de pedidos

Para usar este exemplo, você precisará:

  1. Substituir sua_api_key_aqui pela sua chave de API real
  2. Implementar a lógica para buscar dados do seu banco de dados ou sistema de armazenamento
  3. Adaptar as estruturas de dados conforme necessário para seu caso de uso específico

Executando o Exemplo

Para executar este exemplo:

go run main.go

O servidor será iniciado na porta 8080 e estará pronto para receber requisições.