feat(pagination): add pagination to all list endpoints

Added pagination support to:
- ListCompanies: filter by role, search
- ListProducts: filter by seller, search
- ListOrders: filter by buyer, seller, status
- ListInventory: filter by expiring date, seller

New domain types:
- ProductFilter, ProductPage
- CompanyFilter, CompanyPage
- OrderFilter, OrderPage
- InventoryPage

All endpoints now return paginated responses with:
- items array
- total count
- current page
- page size

Updated MockRepository in both test files to match new signatures
This commit is contained in:
Tiago Yamamoto 2025-12-20 08:37:59 -03:00
parent b713d8fbed
commit 45d34f36c8
10 changed files with 307 additions and 70 deletions

View file

@ -71,9 +71,70 @@ type InventoryItem struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
} }
// InventoryFilter allows filtering by expiration window. // InventoryFilter allows filtering by expiration window with pagination.
type InventoryFilter struct { type InventoryFilter struct {
ExpiringBefore *time.Time ExpiringBefore *time.Time
SellerID *uuid.UUID
Limit int
Offset int
}
// InventoryPage wraps paginated inventory results.
type InventoryPage struct {
Items []InventoryItem `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// ProductFilter captures product listing constraints.
type ProductFilter struct {
SellerID *uuid.UUID
Category string
Search string
Limit int
Offset int
}
// ProductPage wraps paginated product results.
type ProductPage struct {
Products []Product `json:"products"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// CompanyFilter captures company listing constraints.
type CompanyFilter struct {
Role string
Search string
Limit int
Offset int
}
// CompanyPage wraps paginated company results.
type CompanyPage struct {
Companies []Company `json:"companies"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// OrderFilter captures order listing constraints.
type OrderFilter struct {
BuyerID *uuid.UUID
SellerID *uuid.UUID
Status OrderStatus
Limit int
Offset int
}
// OrderPage wraps paginated order results.
type OrderPage struct {
Orders []Order `json:"orders"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
} }
// InventoryAdjustment records manual stock corrections. // InventoryAdjustment records manual stock corrections.

View file

@ -47,12 +47,18 @@ func (h *Handler) CreateCompany(w http.ResponseWriter, r *http.Request) {
// @Success 200 {array} domain.Company // @Success 200 {array} domain.Company
// @Router /api/v1/companies [get] // @Router /api/v1/companies [get]
func (h *Handler) ListCompanies(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListCompanies(w http.ResponseWriter, r *http.Request) {
companies, err := h.svc.ListCompanies(r.Context()) page, pageSize := parsePagination(r)
filter := domain.CompanyFilter{
Role: r.URL.Query().Get("role"),
Search: r.URL.Query().Get("search"),
}
result, err := h.svc.ListCompanies(r.Context(), filter, page, pageSize)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err) writeError(w, http.StatusInternalServerError, err)
return return
} }
writeJSON(w, http.StatusOK, companies) writeJSON(w, http.StatusOK, result)
} }
// GetCompany godoc // GetCompany godoc

View file

@ -42,8 +42,8 @@ func (m *MockRepository) CreateCompany(ctx context.Context, company *domain.Comp
return nil return nil
} }
func (m *MockRepository) ListCompanies(ctx context.Context) ([]domain.Company, error) { func (m *MockRepository) ListCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.Company, int64, error) {
return m.companies, nil return m.companies, int64(len(m.companies)), nil
} }
func (m *MockRepository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) { func (m *MockRepository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
@ -85,8 +85,8 @@ func (m *MockRepository) CreateProduct(ctx context.Context, product *domain.Prod
return nil return nil
} }
func (m *MockRepository) ListProducts(ctx context.Context) ([]domain.Product, error) { func (m *MockRepository) ListProducts(ctx context.Context, filter domain.ProductFilter) ([]domain.Product, int64, error) {
return m.products, nil return m.products, int64(len(m.products)), nil
} }
func (m *MockRepository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) { func (m *MockRepository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
@ -123,8 +123,8 @@ func (m *MockRepository) AdjustInventory(ctx context.Context, productID uuid.UUI
return &domain.InventoryItem{}, nil return &domain.InventoryItem{}, nil
} }
func (m *MockRepository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) { func (m *MockRepository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, int64, error) {
return []domain.InventoryItem{}, nil return []domain.InventoryItem{}, 0, nil
} }
func (m *MockRepository) CreateOrder(ctx context.Context, order *domain.Order) error { func (m *MockRepository) CreateOrder(ctx context.Context, order *domain.Order) error {
@ -134,8 +134,8 @@ func (m *MockRepository) CreateOrder(ctx context.Context, order *domain.Order) e
return nil return nil
} }
func (m *MockRepository) ListOrders(ctx context.Context) ([]domain.Order, error) { func (m *MockRepository) ListOrders(ctx context.Context, filter domain.OrderFilter) ([]domain.Order, int64, error) {
return m.orders, nil return m.orders, int64(len(m.orders)), nil
} }
func (m *MockRepository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { func (m *MockRepository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
@ -259,10 +259,10 @@ func TestListProducts(t *testing.T) {
t.Errorf("expected status %d, got %d", http.StatusOK, rec.Code) t.Errorf("expected status %d, got %d", http.StatusOK, rec.Code)
} }
// Should return empty array // Should return page with empty products array
body := strings.TrimSpace(rec.Body.String()) body := strings.TrimSpace(rec.Body.String())
if body != "[]" { if !strings.Contains(body, `"products":[]`) || !strings.Contains(body, `"total":0`) {
t.Errorf("expected empty array, got %s", body) t.Errorf("expected paginated response with empty products, got %s", body)
} }
} }

View file

@ -51,13 +51,16 @@ func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
// @Success 200 {array} domain.Order // @Success 200 {array} domain.Order
// @Router /api/v1/orders [get] // @Router /api/v1/orders [get]
func (h *Handler) ListOrders(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListOrders(w http.ResponseWriter, r *http.Request) {
orders, err := h.svc.ListOrders(r.Context()) page, pageSize := parsePagination(r)
filter := domain.OrderFilter{}
result, err := h.svc.ListOrders(r.Context(), filter, page, pageSize)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err) writeError(w, http.StatusInternalServerError, err)
return return
} }
writeJSON(w, http.StatusOK, orders) writeJSON(w, http.StatusOK, result)
} }
// GetOrder godoc // GetOrder godoc

View file

@ -49,12 +49,17 @@ func (h *Handler) CreateProduct(w http.ResponseWriter, r *http.Request) {
// @Success 200 {array} domain.Product // @Success 200 {array} domain.Product
// @Router /api/v1/products [get] // @Router /api/v1/products [get]
func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListProducts(w http.ResponseWriter, r *http.Request) {
products, err := h.svc.ListProducts(r.Context()) page, pageSize := parsePagination(r)
filter := domain.ProductFilter{
Search: r.URL.Query().Get("search"),
}
result, err := h.svc.ListProducts(r.Context(), filter, page, pageSize)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err) writeError(w, http.StatusInternalServerError, err)
return return
} }
writeJSON(w, http.StatusOK, products) writeJSON(w, http.StatusOK, result)
} }
// GetProduct godoc // GetProduct godoc
@ -174,6 +179,7 @@ func (h *Handler) DeleteProduct(w http.ResponseWriter, r *http.Request) {
// @Router /api/v1/inventory [get] // @Router /api/v1/inventory [get]
// ListInventory exposes stock with expiring batch filters. // ListInventory exposes stock with expiring batch filters.
func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) { func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) {
page, pageSize := parsePagination(r)
var filter domain.InventoryFilter var filter domain.InventoryFilter
if days := r.URL.Query().Get("expires_in_days"); days != "" { if days := r.URL.Query().Get("expires_in_days"); days != "" {
n, err := strconv.Atoi(days) n, err := strconv.Atoi(days)
@ -185,13 +191,13 @@ func (h *Handler) ListInventory(w http.ResponseWriter, r *http.Request) {
filter.ExpiringBefore = &expires filter.ExpiringBefore = &expires
} }
inventory, err := h.svc.ListInventory(r.Context(), filter) result, err := h.svc.ListInventory(r.Context(), filter, page, pageSize)
if err != nil { if err != nil {
writeError(w, http.StatusInternalServerError, err) writeError(w, http.StatusInternalServerError, err)
return return
} }
writeJSON(w, http.StatusOK, inventory) writeJSON(w, http.StatusOK, result)
} }
// AdjustInventory godoc // AdjustInventory godoc

View file

@ -35,13 +35,41 @@ VALUES (:id, :role, :cnpj, :corporate_name, :license_number, :is_verified, :crea
return err return err
} }
func (r *Repository) ListCompanies(ctx context.Context) ([]domain.Company, error) { func (r *Repository) ListCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.Company, int64, error) {
var companies []domain.Company baseQuery := `FROM companies`
query := `SELECT id, role, cnpj, corporate_name, license_number, is_verified, created_at, updated_at FROM companies ORDER BY created_at DESC` var args []any
if err := r.db.SelectContext(ctx, &companies, query); err != nil { var clauses []string
return nil, err
if filter.Role != "" {
clauses = append(clauses, fmt.Sprintf("role = $%d", len(args)+1))
args = append(args, filter.Role)
} }
return companies, nil if filter.Search != "" {
clauses = append(clauses, fmt.Sprintf("(corporate_name ILIKE $%d OR cnpj ILIKE $%d)", len(args)+1, len(args)+1))
args = append(args, "%"+filter.Search+"%")
}
where := ""
if len(clauses) > 0 {
where = " WHERE " + strings.Join(clauses, " AND ")
}
var total int64
if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery+where, args...); err != nil {
return nil, 0, err
}
if filter.Limit <= 0 {
filter.Limit = 20
}
args = append(args, filter.Limit, filter.Offset)
listQuery := fmt.Sprintf("SELECT id, role, cnpj, corporate_name, license_number, is_verified, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, where, len(args)-1, len(args))
var companies []domain.Company
if err := r.db.SelectContext(ctx, &companies, listQuery, args...); err != nil {
return nil, 0, err
}
return companies, total, nil
} }
func (r *Repository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) { func (r *Repository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
@ -133,13 +161,41 @@ VALUES (:id, :seller_id, :name, :description, :batch, :expires_at, :price_cents,
return err return err
} }
func (r *Repository) ListProducts(ctx context.Context) ([]domain.Product, error) { func (r *Repository) ListProducts(ctx context.Context, filter domain.ProductFilter) ([]domain.Product, int64, error) {
var products []domain.Product baseQuery := `FROM products`
query := `SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at FROM products ORDER BY created_at DESC` var args []any
if err := r.db.SelectContext(ctx, &products, query); err != nil { var clauses []string
return nil, err
if filter.SellerID != nil {
clauses = append(clauses, fmt.Sprintf("seller_id = $%d", len(args)+1))
args = append(args, *filter.SellerID)
} }
return products, nil if filter.Search != "" {
clauses = append(clauses, fmt.Sprintf("name ILIKE $%d", len(args)+1))
args = append(args, "%"+filter.Search+"%")
}
where := ""
if len(clauses) > 0 {
where = " WHERE " + strings.Join(clauses, " AND ")
}
var total int64
if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery+where, args...); err != nil {
return nil, 0, err
}
if filter.Limit <= 0 {
filter.Limit = 20
}
args = append(args, filter.Limit, filter.Offset)
listQuery := fmt.Sprintf("SELECT id, seller_id, name, description, batch, expires_at, price_cents, stock, created_at, updated_at %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, where, len(args)-1, len(args))
var products []domain.Product
if err := r.db.SelectContext(ctx, &products, listQuery, args...); err != nil {
return nil, 0, err
}
return products, total, nil
} }
func (r *Repository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) { func (r *Repository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
@ -244,7 +300,40 @@ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)`
return tx.Commit() return tx.Commit()
} }
func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) { func (r *Repository) ListOrders(ctx context.Context, filter domain.OrderFilter) ([]domain.Order, int64, error) {
baseQuery := `FROM orders`
var args []any
var clauses []string
if filter.BuyerID != nil {
clauses = append(clauses, fmt.Sprintf("buyer_id = $%d", len(args)+1))
args = append(args, *filter.BuyerID)
}
if filter.SellerID != nil {
clauses = append(clauses, fmt.Sprintf("seller_id = $%d", len(args)+1))
args = append(args, *filter.SellerID)
}
if filter.Status != "" {
clauses = append(clauses, fmt.Sprintf("status = $%d", len(args)+1))
args = append(args, filter.Status)
}
where := ""
if len(clauses) > 0 {
where = " WHERE " + strings.Join(clauses, " AND ")
}
var total int64
if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery+where, args...); err != nil {
return nil, 0, err
}
if filter.Limit <= 0 {
filter.Limit = 20
}
args = append(args, filter.Limit, filter.Offset)
listQuery := fmt.Sprintf(`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 %s%s ORDER BY created_at DESC LIMIT $%d OFFSET $%d`, baseQuery, where, len(args)-1, len(args))
var rows []struct { var rows []struct {
ID uuid.UUID `db:"id"` ID uuid.UUID `db:"id"`
BuyerID uuid.UUID `db:"buyer_id"` BuyerID uuid.UUID `db:"buyer_id"`
@ -263,9 +352,9 @@ func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) {
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_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 { if err := r.db.SelectContext(ctx, &rows, listQuery, args...); err != nil {
return nil, err return nil, 0, err
} }
orders := make([]domain.Order, 0, len(rows)) orders := make([]domain.Order, 0, len(rows))
@ -273,7 +362,7 @@ func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) {
for _, row := range rows { for _, row := range rows {
var items []domain.OrderItem var items []domain.OrderItem
if err := r.db.SelectContext(ctx, &items, itemQuery, row.ID); err != nil { if err := r.db.SelectContext(ctx, &items, itemQuery, row.ID); err != nil {
return nil, err return nil, 0, err
} }
orders = append(orders, domain.Order{ orders = append(orders, domain.Order{
ID: row.ID, ID: row.ID,
@ -297,7 +386,7 @@ func (r *Repository) ListOrders(ctx context.Context) ([]domain.Order, error) {
UpdatedAt: row.UpdatedAt, UpdatedAt: row.UpdatedAt,
}) })
} }
return orders, nil return orders, total, nil
} }
func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { func (r *Repository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
@ -480,25 +569,40 @@ func (r *Repository) AdjustInventory(ctx context.Context, productID uuid.UUID, d
}, nil }, nil
} }
func (r *Repository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) { func (r *Repository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, int64, error) {
baseQuery := `FROM products`
args := []any{} args := []any{}
clauses := []string{} clauses := []string{}
if filter.ExpiringBefore != nil { if filter.ExpiringBefore != nil {
clauses = append(clauses, fmt.Sprintf("expires_at <= $%d", len(args)+1)) clauses = append(clauses, fmt.Sprintf("expires_at <= $%d", len(args)+1))
args = append(args, *filter.ExpiringBefore) args = append(args, *filter.ExpiringBefore)
} }
if filter.SellerID != nil {
clauses = append(clauses, fmt.Sprintf("seller_id = $%d", len(args)+1))
args = append(args, *filter.SellerID)
}
where := "" where := ""
if len(clauses) > 0 { if len(clauses) > 0 {
where = " WHERE " + strings.Join(clauses, " AND ") where = " WHERE " + strings.Join(clauses, " AND ")
} }
query := fmt.Sprintf(`SELECT id AS product_id, seller_id, name, batch, expires_at, stock AS quantity, price_cents, updated_at FROM products%s ORDER BY expires_at ASC`, where) var total int64
var items []domain.InventoryItem if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery+where, args...); err != nil {
if err := r.db.SelectContext(ctx, &items, query, args...); err != nil { return nil, 0, err
return nil, err
} }
return items, nil
if filter.Limit <= 0 {
filter.Limit = 20
}
args = append(args, filter.Limit, filter.Offset)
listQuery := fmt.Sprintf(`SELECT id AS product_id, seller_id, name, batch, expires_at, stock AS quantity, price_cents, updated_at %s%s ORDER BY expires_at ASC LIMIT $%d OFFSET $%d`, baseQuery, where, len(args)-1, len(args))
var items []domain.InventoryItem
if err := r.db.SelectContext(ctx, &items, listQuery, args...); err != nil {
return nil, 0, err
}
return items, total, nil
} }
func (r *Repository) CreateUser(ctx context.Context, user *domain.User) error { func (r *Repository) CreateUser(ctx context.Context, user *domain.User) error {

View file

@ -17,21 +17,22 @@ import (
// Repository defines DB contract for the core entities. // Repository defines DB contract for the core entities.
type Repository interface { type Repository interface {
CreateCompany(ctx context.Context, company *domain.Company) error CreateCompany(ctx context.Context, company *domain.Company) error
ListCompanies(ctx context.Context) ([]domain.Company, error) ListCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.Company, int64, error)
GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error)
UpdateCompany(ctx context.Context, company *domain.Company) error UpdateCompany(ctx context.Context, company *domain.Company) error
DeleteCompany(ctx context.Context, id uuid.UUID) error DeleteCompany(ctx context.Context, id uuid.UUID) error
CreateProduct(ctx context.Context, product *domain.Product) error CreateProduct(ctx context.Context, product *domain.Product) error
ListProducts(ctx context.Context) ([]domain.Product, error) ListProducts(ctx context.Context, filter domain.ProductFilter) ([]domain.Product, int64, error)
GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error)
UpdateProduct(ctx context.Context, product *domain.Product) error UpdateProduct(ctx context.Context, product *domain.Product) error
DeleteProduct(ctx context.Context, id uuid.UUID) error DeleteProduct(ctx context.Context, id uuid.UUID) error
AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, 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) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, int64, error)
CreateOrder(ctx context.Context, order *domain.Order) error CreateOrder(ctx context.Context, order *domain.Order) error
ListOrders(ctx context.Context) ([]domain.Order, error) ListOrders(ctx context.Context, filter domain.OrderFilter) ([]domain.Order, int64, error)
GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error)
UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error UpdateOrderStatus(ctx context.Context, id uuid.UUID, status domain.OrderStatus) error
DeleteOrder(ctx context.Context, id uuid.UUID) error DeleteOrder(ctx context.Context, id uuid.UUID) error
@ -86,8 +87,20 @@ func (s *Service) RegisterCompany(ctx context.Context, company *domain.Company)
return s.repo.CreateCompany(ctx, company) return s.repo.CreateCompany(ctx, company)
} }
func (s *Service) ListCompanies(ctx context.Context) ([]domain.Company, error) { func (s *Service) ListCompanies(ctx context.Context, filter domain.CompanyFilter, page, pageSize int) (*domain.CompanyPage, error) {
return s.repo.ListCompanies(ctx) if pageSize <= 0 {
pageSize = 20
}
if page <= 0 {
page = 1
}
filter.Limit = pageSize
filter.Offset = (page - 1) * pageSize
companies, total, err := s.repo.ListCompanies(ctx, filter)
if err != nil {
return nil, err
}
return &domain.CompanyPage{Companies: companies, Total: total, Page: page, PageSize: pageSize}, nil
} }
func (s *Service) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) { func (s *Service) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
@ -107,8 +120,20 @@ func (s *Service) RegisterProduct(ctx context.Context, product *domain.Product)
return s.repo.CreateProduct(ctx, product) return s.repo.CreateProduct(ctx, product)
} }
func (s *Service) ListProducts(ctx context.Context) ([]domain.Product, error) { func (s *Service) ListProducts(ctx context.Context, filter domain.ProductFilter, page, pageSize int) (*domain.ProductPage, error) {
return s.repo.ListProducts(ctx) if pageSize <= 0 {
pageSize = 20
}
if page <= 0 {
page = 1
}
filter.Limit = pageSize
filter.Offset = (page - 1) * pageSize
products, total, err := s.repo.ListProducts(ctx, filter)
if err != nil {
return nil, err
}
return &domain.ProductPage{Products: products, Total: total, Page: page, PageSize: pageSize}, nil
} }
func (s *Service) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) { func (s *Service) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
@ -123,8 +148,20 @@ func (s *Service) DeleteProduct(ctx context.Context, id uuid.UUID) error {
return s.repo.DeleteProduct(ctx, id) return s.repo.DeleteProduct(ctx, id)
} }
func (s *Service) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) { func (s *Service) ListInventory(ctx context.Context, filter domain.InventoryFilter, page, pageSize int) (*domain.InventoryPage, error) {
return s.repo.ListInventory(ctx, filter) if pageSize <= 0 {
pageSize = 20
}
if page <= 0 {
page = 1
}
filter.Limit = pageSize
filter.Offset = (page - 1) * pageSize
items, total, err := s.repo.ListInventory(ctx, filter)
if err != nil {
return nil, err
}
return &domain.InventoryPage{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
} }
func (s *Service) AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error) { func (s *Service) AdjustInventory(ctx context.Context, productID uuid.UUID, delta int64, reason string) (*domain.InventoryItem, error) {
@ -137,8 +174,20 @@ func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error {
return s.repo.CreateOrder(ctx, order) return s.repo.CreateOrder(ctx, order)
} }
func (s *Service) ListOrders(ctx context.Context) ([]domain.Order, error) { func (s *Service) ListOrders(ctx context.Context, filter domain.OrderFilter, page, pageSize int) (*domain.OrderPage, error) {
return s.repo.ListOrders(ctx) if pageSize <= 0 {
pageSize = 20
}
if page <= 0 {
page = 1
}
filter.Limit = pageSize
filter.Offset = (page - 1) * pageSize
orders, total, err := s.repo.ListOrders(ctx, filter)
if err != nil {
return nil, err
}
return &domain.OrderPage{Orders: orders, Total: total, Page: page, PageSize: pageSize}, nil
} }
func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { func (s *Service) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {

View file

@ -38,8 +38,8 @@ func (m *MockRepository) CreateCompany(ctx context.Context, company *domain.Comp
return nil return nil
} }
func (m *MockRepository) ListCompanies(ctx context.Context) ([]domain.Company, error) { func (m *MockRepository) ListCompanies(ctx context.Context, filter domain.CompanyFilter) ([]domain.Company, int64, error) {
return m.companies, nil return m.companies, int64(len(m.companies)), nil
} }
func (m *MockRepository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) { func (m *MockRepository) GetCompany(ctx context.Context, id uuid.UUID) (*domain.Company, error) {
@ -79,8 +79,8 @@ func (m *MockRepository) CreateProduct(ctx context.Context, product *domain.Prod
return nil return nil
} }
func (m *MockRepository) ListProducts(ctx context.Context) ([]domain.Product, error) { func (m *MockRepository) ListProducts(ctx context.Context, filter domain.ProductFilter) ([]domain.Product, int64, error) {
return m.products, nil return m.products, int64(len(m.products)), nil
} }
func (m *MockRepository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) { func (m *MockRepository) GetProduct(ctx context.Context, id uuid.UUID) (*domain.Product, error) {
@ -116,8 +116,8 @@ func (m *MockRepository) AdjustInventory(ctx context.Context, productID uuid.UUI
return &domain.InventoryItem{ProductID: productID, Quantity: delta}, nil return &domain.InventoryItem{ProductID: productID, Quantity: delta}, nil
} }
func (m *MockRepository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, error) { func (m *MockRepository) ListInventory(ctx context.Context, filter domain.InventoryFilter) ([]domain.InventoryItem, int64, error) {
return []domain.InventoryItem{}, nil return []domain.InventoryItem{}, 0, nil
} }
// Order methods // Order methods
@ -126,8 +126,8 @@ func (m *MockRepository) CreateOrder(ctx context.Context, order *domain.Order) e
return nil return nil
} }
func (m *MockRepository) ListOrders(ctx context.Context) ([]domain.Order, error) { func (m *MockRepository) ListOrders(ctx context.Context, filter domain.OrderFilter) ([]domain.Order, int64, error) {
return m.orders, nil return m.orders, int64(len(m.orders)), nil
} }
func (m *MockRepository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) { func (m *MockRepository) GetOrder(ctx context.Context, id uuid.UUID) (*domain.Order, error) {
@ -296,13 +296,13 @@ func TestListCompanies(t *testing.T) {
svc, _ := newTestService() svc, _ := newTestService()
ctx := context.Background() ctx := context.Background()
companies, err := svc.ListCompanies(ctx) page, err := svc.ListCompanies(ctx, domain.CompanyFilter{}, 1, 20)
if err != nil { if err != nil {
t.Fatalf("failed to list companies: %v", err) t.Fatalf("failed to list companies: %v", err)
} }
if len(companies) != 0 { if len(page.Companies) != 0 {
t.Errorf("expected 0 companies, got %d", len(companies)) t.Errorf("expected 0 companies, got %d", len(page.Companies))
} }
} }
@ -359,13 +359,13 @@ func TestListProducts(t *testing.T) {
svc, _ := newTestService() svc, _ := newTestService()
ctx := context.Background() ctx := context.Background()
products, err := svc.ListProducts(ctx) page, err := svc.ListProducts(ctx, domain.ProductFilter{}, 1, 20)
if err != nil { if err != nil {
t.Fatalf("failed to list products: %v", err) t.Fatalf("failed to list products: %v", err)
} }
if len(products) != 0 { if len(page.Products) != 0 {
t.Errorf("expected 0 products, got %d", len(products)) t.Errorf("expected 0 products, got %d", len(page.Products))
} }
} }

2
marketplace/vite.config.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

View file

@ -0,0 +1,6 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
});