perf: add pagination, filters, and fix Django sync pagination
This commit is contained in:
@@ -4,27 +4,97 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"rrbec_server/internal/models"
|
||||
"rrbec_server/internal/repository"
|
||||
"rrbec_server/internal/service"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
svc *service.Service
|
||||
svc *service.Service
|
||||
syncer interface{ SyncProductsOnly() }
|
||||
}
|
||||
|
||||
func NewHandler(svc *service.Service) *Handler {
|
||||
return &Handler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *Handler) SetSyncer(syncer interface{ SyncProductsOnly() }) {
|
||||
h.syncer = syncer
|
||||
}
|
||||
|
||||
func parsePagination(c *gin.Context) *repository.PaginationParams {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
||||
sort := c.DefaultQuery("sort", "id ASC")
|
||||
|
||||
params := &repository.PaginationParams{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Sort: sort,
|
||||
Filters: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if name := c.Query("name"); name != "" {
|
||||
params.Filters["name"] = name
|
||||
}
|
||||
if active := c.Query("active"); active != "" {
|
||||
if b, err := strconv.ParseBool(active); err == nil {
|
||||
params.Filters["active"] = b
|
||||
}
|
||||
}
|
||||
if status := c.Query("status"); status != "" {
|
||||
params.Filters["status"] = status
|
||||
}
|
||||
if category := c.Query("category"); category != "" {
|
||||
if c, err := strconv.ParseUint(category, 10, 64); err == nil {
|
||||
params.Filters["category"] = uint(c)
|
||||
}
|
||||
}
|
||||
if cuisine := c.Query("cuisine"); cuisine != "" {
|
||||
if b, err := strconv.ParseBool(cuisine); err == nil {
|
||||
params.Filters["cuisine"] = b
|
||||
}
|
||||
}
|
||||
if dateFrom := c.Query("date_from"); dateFrom != "" {
|
||||
if t, err := time.Parse("2006-01-02", dateFrom); err == nil {
|
||||
params.Filters["date_from"] = t
|
||||
}
|
||||
}
|
||||
if dateTo := c.Query("date_to"); dateTo != "" {
|
||||
if t, err := time.Parse("2006-01-02", dateTo); err == nil {
|
||||
params.Filters["date_to"] = t
|
||||
}
|
||||
}
|
||||
if comandaID := c.Query("comanda_id"); comandaID != "" {
|
||||
if c, err := strconv.ParseUint(comandaID, 10, 64); err == nil {
|
||||
params.Filters["comanda_id"] = uint(c)
|
||||
}
|
||||
}
|
||||
if canceled := c.Query("canceled"); canceled != "" {
|
||||
if b, err := strconv.ParseBool(canceled); err == nil {
|
||||
params.Filters["canceled"] = b
|
||||
}
|
||||
}
|
||||
if delivered := c.Query("delivered"); delivered != "" {
|
||||
if b, err := strconv.ParseBool(delivered); err == nil {
|
||||
params.Filters["delivered"] = b
|
||||
}
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
func (h *Handler) GetProducts(c *gin.Context) {
|
||||
products, err := h.svc.GetProducts()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetProducts(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, products)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateProduct(c *gin.Context) {
|
||||
@@ -65,21 +135,23 @@ func (h *Handler) UpdateProduct(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) GetMesas(c *gin.Context) {
|
||||
mesas, err := h.svc.GetMesas()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetMesas(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, mesas)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) GetCategories(c *gin.Context) {
|
||||
categories, err := h.svc.GetCategories()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetCategories(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, categories)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateCategory(c *gin.Context) {
|
||||
@@ -120,30 +192,33 @@ func (h *Handler) UpdateCategory(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) GetTypePayments(c *gin.Context) {
|
||||
types, err := h.svc.GetTypePayments()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetTypePayments(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, types)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) GetClients(c *gin.Context) {
|
||||
clients, err := h.svc.GetClients()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetClients(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, clients)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) GetOrders(c *gin.Context) {
|
||||
orders, err := h.svc.GetOrders()
|
||||
params := parsePagination(c)
|
||||
result, err := h.svc.GetOrders(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, orders)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateOrder(c *gin.Context) {
|
||||
@@ -244,12 +319,16 @@ func (h *Handler) SetOrderCanceled(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) GetPayments(c *gin.Context) {
|
||||
payments, err := h.svc.GetPayments()
|
||||
params := parsePagination(c)
|
||||
if c.Query("sort") == "" {
|
||||
params.Sort = "id DESC"
|
||||
}
|
||||
result, err := h.svc.GetPayments(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, payments)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateComanda(c *gin.Context) {
|
||||
@@ -400,12 +479,16 @@ func (h *Handler) PagarComanda(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *Handler) GetComandas(c *gin.Context) {
|
||||
comandas, err := h.svc.GetComandas()
|
||||
params := parsePagination(c)
|
||||
if c.Query("sort") == "" {
|
||||
params.Sort = "id DESC"
|
||||
}
|
||||
result, err := h.svc.GetComandas(params)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, comandas)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (h *Handler) GetComandaByID(c *gin.Context) {
|
||||
@@ -455,3 +538,12 @@ func (h *Handler) GetCurrentUser(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"user_id": userID})
|
||||
}
|
||||
|
||||
func (h *Handler) ResyncProducts(c *gin.Context) {
|
||||
if h.syncer == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "syncer not configured"})
|
||||
return
|
||||
}
|
||||
go h.syncer.SyncProductsOnly()
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Product resync started in background"})
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ type Comanda struct {
|
||||
DtOpen time.Time `json:"dt_open"`
|
||||
DtClose *time.Time `json:"dt_close"`
|
||||
|
||||
// Nested items from Django (not stored in coma nda table)
|
||||
Items []ProductComanda `gorm:"-" json:"items"`
|
||||
// Nested items from Django (not stored in comanda table)
|
||||
Items []ProductComanda `gorm:"foreignKey:ComandaID" json:"items"`
|
||||
}
|
||||
|
||||
type ProductComanda struct {
|
||||
|
||||
@@ -4,9 +4,131 @@ import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
"math"
|
||||
"rrbec_server/internal/models"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PaginationParams struct {
|
||||
Page int
|
||||
Limit int
|
||||
Sort string
|
||||
Filters map[string]interface{}
|
||||
}
|
||||
|
||||
type PaginatedResponse struct {
|
||||
Data interface{} `json:"data"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
func (p *PaginationParams) Apply(db *gorm.DB, model interface{}) (*gorm.DB, int64, error) {
|
||||
if p.Page <= 0 {
|
||||
p.Page = 1
|
||||
}
|
||||
if p.Limit <= 0 {
|
||||
p.Limit = 50
|
||||
}
|
||||
if p.Limit > 200 {
|
||||
p.Limit = 200
|
||||
}
|
||||
|
||||
query := db.Model(model)
|
||||
|
||||
for key, value := range p.Filters {
|
||||
if key == "status" {
|
||||
if s, ok := value.(string); ok && s != "" {
|
||||
query = query.Where("status = ?", s)
|
||||
}
|
||||
} else if key == "active" {
|
||||
if b, ok := value.(bool); ok {
|
||||
query = query.Where("active = ?", b)
|
||||
}
|
||||
} else if key == "category" {
|
||||
if c, ok := value.(uint); ok && c > 0 {
|
||||
query = query.Where("category = ?", c)
|
||||
}
|
||||
} else if key == "cuisine" {
|
||||
if b, ok := value.(bool); ok {
|
||||
query = query.Where("cuisine = ?", b)
|
||||
}
|
||||
} else if key == "date_from" {
|
||||
if t, ok := value.(time.Time); ok {
|
||||
query = query.Where("date_time >= ? OR dt_open >= ? OR created_at >= ?", t, t, t)
|
||||
}
|
||||
} else if key == "date_to" {
|
||||
if t, ok := value.(time.Time); ok {
|
||||
query = query.Where("date_time <= ? OR dt_open <= ? OR created_at <= ?", t, t, t)
|
||||
}
|
||||
} else if key == "comanda_id" {
|
||||
if c, ok := value.(uint); ok && c > 0 {
|
||||
query = query.Where("comanda_id = ?", c)
|
||||
}
|
||||
} else if key == "name" {
|
||||
if n, ok := value.(string); ok && n != "" {
|
||||
query = query.Where("name LIKE ?", "%"+n+"%")
|
||||
}
|
||||
} else if key == "canceled" {
|
||||
if v, ok := value.(bool); ok {
|
||||
if v {
|
||||
query = query.Where("canceled IS NOT NULL")
|
||||
} else {
|
||||
query = query.Where("canceled IS NULL")
|
||||
}
|
||||
}
|
||||
} else if key == "delivered" {
|
||||
if v, ok := value.(bool); ok {
|
||||
if v {
|
||||
query = query.Where("delivered IS NOT NULL")
|
||||
} else {
|
||||
query = query.Where("delivered IS NULL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
sort := "id ASC"
|
||||
if p.Sort != "" {
|
||||
sort = p.Sort
|
||||
}
|
||||
|
||||
offset := (p.Page - 1) * p.Limit
|
||||
query = query.Order(sort).Offset(offset).Limit(p.Limit)
|
||||
|
||||
return query, total, nil
|
||||
}
|
||||
|
||||
func (p *PaginationParams) Paginate(db *gorm.DB, model interface{}, result interface{}) (*PaginatedResponse, error) {
|
||||
query, total, err := p.Apply(db, model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := query.Find(result).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(p.Limit)))
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
return &PaginatedResponse{
|
||||
Data: result,
|
||||
Total: total,
|
||||
Page: p.Page,
|
||||
Limit: p.Limit,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
@@ -16,10 +138,9 @@ func NewRepository(db *gorm.DB) *Repository {
|
||||
}
|
||||
|
||||
// Mesa
|
||||
func (r *Repository) GetMesas() ([]models.Mesa, error) {
|
||||
func (r *Repository) GetMesas(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var mesas []models.Mesa
|
||||
err := r.db.Find(&mesas).Error
|
||||
return mesas, err
|
||||
return params.Paginate(r.db, &models.Mesa{}, &mesas)
|
||||
}
|
||||
|
||||
func (r *Repository) CreateMesa(mesa *models.Mesa) error {
|
||||
@@ -31,10 +152,9 @@ func (r *Repository) SaveMesa(mesa *models.Mesa) error {
|
||||
}
|
||||
|
||||
// Client
|
||||
func (r *Repository) GetClients() ([]models.Client, error) {
|
||||
func (r *Repository) GetClients(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var clients []models.Client
|
||||
err := r.db.Find(&clients).Error
|
||||
return clients, err
|
||||
return params.Paginate(r.db, &models.Client{}, &clients)
|
||||
}
|
||||
|
||||
func (r *Repository) CreateClient(client *models.Client) error {
|
||||
@@ -46,10 +166,13 @@ func (r *Repository) SaveClient(client *models.Client) error {
|
||||
}
|
||||
|
||||
// Product
|
||||
func (r *Repository) GetProducts() ([]models.Product, error) {
|
||||
func (r *Repository) GetProducts(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var products []models.Product
|
||||
err := r.db.Find(&products).Error
|
||||
return products, err
|
||||
return params.Paginate(r.db, &models.Product{}, &products)
|
||||
}
|
||||
|
||||
func (r *Repository) GetProductsAll(products *[]models.Product) error {
|
||||
return r.db.Find(products).Error
|
||||
}
|
||||
|
||||
func (r *Repository) SaveProduct(product *models.Product) error {
|
||||
@@ -88,20 +211,33 @@ func (r *Repository) CreateProduct(product *models.Product) error {
|
||||
}
|
||||
|
||||
// Comanda
|
||||
func (r *Repository) GetComandas() ([]models.Comanda, error) {
|
||||
func (r *Repository) GetComandas(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var comandas []models.Comanda
|
||||
if err := r.db.Find(&comandas).Error; err != nil {
|
||||
if params == nil {
|
||||
params = &PaginationParams{Page: 1, Limit: 50}
|
||||
}
|
||||
|
||||
query, total, err := params.Apply(r.db, &models.Comanda{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For each comanda, load its items
|
||||
for i := range comandas {
|
||||
var items []models.ProductComanda
|
||||
r.db.Where("comanda_id = ?", comandas[i].ID).Find(&items)
|
||||
comandas[i].Items = items
|
||||
if err := query.Preload("Items").Find(&comandas).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comandas, nil
|
||||
totalPages := int(math.Ceil(float64(total) / float64(params.Limit)))
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
return &PaginatedResponse{
|
||||
Data: comandas,
|
||||
Total: total,
|
||||
Page: params.Page,
|
||||
Limit: params.Limit,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateComanda(comanda *models.Comanda) error {
|
||||
@@ -128,16 +264,14 @@ func (r *Repository) SaveTypePay(tp *models.TypePay) error {
|
||||
return r.db.Save(tp).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetCategories() ([]models.Category, error) {
|
||||
func (r *Repository) GetCategories(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var categories []models.Category
|
||||
err := r.db.Find(&categories).Error
|
||||
return categories, err
|
||||
return params.Paginate(r.db, &models.Category{}, &categories)
|
||||
}
|
||||
|
||||
func (r *Repository) GetTypePayments() ([]models.TypePay, error) {
|
||||
func (r *Repository) GetTypePayments(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var types []models.TypePay
|
||||
err := r.db.Find(&types).Error
|
||||
return types, err
|
||||
return params.Paginate(r.db, &models.TypePay{}, &types)
|
||||
}
|
||||
|
||||
func (r *Repository) GetComandaByID(id uint) (*models.Comanda, error) {
|
||||
@@ -172,10 +306,9 @@ func (r *Repository) DeleteItem(itemID uint) error {
|
||||
return r.db.Delete(&models.ProductComanda{}, itemID).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetOrders() ([]models.Order, error) {
|
||||
func (r *Repository) GetOrders(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var orders []models.Order
|
||||
err := r.db.Find(&orders).Error
|
||||
return orders, err
|
||||
return params.Paginate(r.db, &models.Order{}, &orders)
|
||||
}
|
||||
|
||||
func (r *Repository) SaveOrder(order *models.Order) error {
|
||||
@@ -216,10 +349,9 @@ func (r *Repository) UpdateOrderFields(id uint, updates map[string]interface{})
|
||||
return r.db.Model(&models.Order{}).Where("id = ?", id).Updates(mappedUpdates).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetPayments() ([]models.Payment, error) {
|
||||
func (r *Repository) GetPayments(params *PaginationParams) (*PaginatedResponse, error) {
|
||||
var payments []models.Payment
|
||||
err := r.db.Find(&payments).Error
|
||||
return payments, err
|
||||
return params.Paginate(r.db, &models.Payment{}, &payments)
|
||||
}
|
||||
|
||||
func (r *Repository) CreatePayment(payment *models.Payment) error {
|
||||
|
||||
@@ -122,8 +122,8 @@ func (s *Service) SyncWithCloud() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetProducts() ([]models.Product, error) {
|
||||
return s.repo.GetProducts()
|
||||
func (s *Service) GetProducts(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetProducts(params)
|
||||
}
|
||||
|
||||
func (s *Service) CreateProduct(product *models.Product) error {
|
||||
@@ -134,20 +134,20 @@ func (s *Service) UpdateProduct(id uint, updates map[string]interface{}) error {
|
||||
return s.repo.UpdateProductFields(id, updates)
|
||||
}
|
||||
|
||||
func (s *Service) GetMesas() ([]models.Mesa, error) {
|
||||
return s.repo.GetMesas()
|
||||
func (s *Service) GetMesas(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetMesas(params)
|
||||
}
|
||||
|
||||
func (s *Service) GetComandas() ([]models.Comanda, error) {
|
||||
return s.repo.GetComandas()
|
||||
func (s *Service) GetComandas(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetComandas(params)
|
||||
}
|
||||
|
||||
func (s *Service) GetComandaByID(id uint) (*models.Comanda, error) {
|
||||
return s.repo.GetComandaByID(id)
|
||||
}
|
||||
|
||||
func (s *Service) GetCategories() ([]models.Category, error) {
|
||||
return s.repo.GetCategories()
|
||||
func (s *Service) GetCategories(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetCategories(params)
|
||||
}
|
||||
|
||||
func (s *Service) CreateCategory(cat *models.Category) error {
|
||||
@@ -158,16 +158,16 @@ func (s *Service) UpdateCategory(id uint, updates map[string]interface{}) error
|
||||
return s.repo.UpdateCategoryFields(id, updates)
|
||||
}
|
||||
|
||||
func (s *Service) GetTypePayments() ([]models.TypePay, error) {
|
||||
return s.repo.GetTypePayments()
|
||||
func (s *Service) GetTypePayments(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetTypePayments(params)
|
||||
}
|
||||
|
||||
func (s *Service) GetClients() ([]models.Client, error) {
|
||||
return s.repo.GetClients()
|
||||
func (s *Service) GetClients(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetClients(params)
|
||||
}
|
||||
|
||||
func (s *Service) GetOrders() ([]models.Order, error) {
|
||||
return s.repo.GetOrders()
|
||||
func (s *Service) GetOrders(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetOrders(params)
|
||||
}
|
||||
|
||||
func (s *Service) CreateOrder(order *models.Order) error {
|
||||
@@ -199,8 +199,8 @@ func (s *Service) SetOrderCanceled(id uint) error {
|
||||
return s.repo.UpdateOrderFields(id, map[string]interface{}{"canceled": &now})
|
||||
}
|
||||
|
||||
func (s *Service) GetPayments() ([]models.Payment, error) {
|
||||
return s.repo.GetPayments()
|
||||
func (s *Service) GetPayments(params *repository.PaginationParams) (*repository.PaginatedResponse, error) {
|
||||
return s.repo.GetPayments(params)
|
||||
}
|
||||
|
||||
func (s *Service) DeleteItem(itemID uint) error {
|
||||
|
||||
@@ -288,48 +288,76 @@ func (s *Syncer) InitialSync() {
|
||||
log.Println("Initial sync completed successfully.")
|
||||
}
|
||||
|
||||
func (s *Syncer) fetchFromDjango(endpoint string, target interface{}) {
|
||||
func (s *Syncer) fetchAllFromDjango(endpoint string, target interface{}) {
|
||||
endpoint = strings.Trim(endpoint, "/")
|
||||
|
||||
url := ""
|
||||
if strings.Contains(endpoint, "?") {
|
||||
url = fmt.Sprintf("%s/api/v1/%s", s.djangoURL, endpoint)
|
||||
} else {
|
||||
url = fmt.Sprintf("%s/api/v1/%s/", s.djangoURL, endpoint)
|
||||
}
|
||||
url := fmt.Sprintf("%s/api/v1/%s/", s.djangoURL, endpoint)
|
||||
|
||||
token := s.accessToken
|
||||
if token == "" {
|
||||
token = s.masterPass
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch %s: %v", endpoint, err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
allData := []json.RawMessage{}
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
for url != "" {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, target); err != nil {
|
||||
var paginated struct {
|
||||
Results json.RawMessage `json:"results"`
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch %s: %v", endpoint, err)
|
||||
return
|
||||
}
|
||||
if err2 := json.Unmarshal(bodyBytes, &paginated); err2 == nil && paginated.Results != nil {
|
||||
if err3 := json.Unmarshal(paginated.Results, target); err3 != nil {
|
||||
log.Printf("Failed to decode paginated %s: %v", endpoint, err3)
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
if s.IsTokenExpired(bodyBytes) {
|
||||
log.Printf("Token expired, refreshing...")
|
||||
if err := s.RefreshAccessToken(); err == nil {
|
||||
token = s.accessToken
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var paginated struct {
|
||||
Results json.RawMessage `json:"results"`
|
||||
Next *string `json:"next"`
|
||||
Previous *string `json:"previous"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bodyBytes, &paginated); err == nil && paginated.Results != nil {
|
||||
var items []json.RawMessage
|
||||
if err := json.Unmarshal(paginated.Results, &items); err == nil {
|
||||
allData = append(allData, items...)
|
||||
}
|
||||
if paginated.Next != nil && *paginated.Next != "" {
|
||||
url = *paginated.Next
|
||||
} else {
|
||||
url = ""
|
||||
}
|
||||
} else {
|
||||
log.Printf("Failed to decode %s: %v", endpoint, err)
|
||||
var items []json.RawMessage
|
||||
if err := json.Unmarshal(bodyBytes, &items); err == nil {
|
||||
allData = append(allData, items...)
|
||||
}
|
||||
url = ""
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Synchronized %s from cloud", endpoint)
|
||||
if len(allData) > 0 {
|
||||
combined, _ := json.Marshal(allData)
|
||||
json.Unmarshal(combined, target)
|
||||
}
|
||||
|
||||
log.Printf("Synchronized %d %s from cloud", len(allData), endpoint)
|
||||
}
|
||||
|
||||
func (s *Syncer) fetchFromDjango(endpoint string, target interface{}) {
|
||||
s.fetchAllFromDjango(endpoint, target)
|
||||
}
|
||||
|
||||
func (s *Syncer) fetchFromDjangoRaw(endpoint string) []byte {
|
||||
@@ -866,6 +894,75 @@ func (s *Syncer) sendMultipartRequest(method, endpoint string, data map[string]i
|
||||
return nil, fmt.Errorf("django returned status: %d | Response: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
func (s *Syncer) SyncProductsOnly() {
|
||||
log.Println("Starting products-only sync...")
|
||||
|
||||
var products []models.Product
|
||||
s.fetchAllFromDjango("products", &products)
|
||||
log.Printf("Fetched %d products from Django", len(products))
|
||||
|
||||
// Build a map of cloud products by ID
|
||||
cloudMap := make(map[uint]models.Product)
|
||||
for _, p := range products {
|
||||
cloudMap[p.ID] = p
|
||||
}
|
||||
|
||||
// Get all local products
|
||||
var localProducts []models.Product
|
||||
s.repo.GetProductsAll(&localProducts)
|
||||
|
||||
localMap := make(map[uint]models.Product)
|
||||
for _, p := range localProducts {
|
||||
localMap[p.ID] = p
|
||||
}
|
||||
|
||||
created := 0
|
||||
updated := 0
|
||||
failed := 0
|
||||
for _, cp := range products {
|
||||
cp.Sincronizado = true
|
||||
if cp.UUID == "" {
|
||||
if lp, exists := localMap[cp.ID]; exists && lp.UUID != "" {
|
||||
cp.UUID = lp.UUID
|
||||
} else {
|
||||
cp.UUID = uuid.New().String()
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, exists := localMap[cp.ID]; exists {
|
||||
err = s.repo.SaveProduct(&cp)
|
||||
if err == nil {
|
||||
updated++
|
||||
}
|
||||
} else {
|
||||
err = s.repo.CreateProduct(&cp)
|
||||
if err == nil {
|
||||
created++
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
failed++
|
||||
log.Printf("ERROR syncing product %d (%s): %v", cp.ID, cp.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete local products that no longer exist in cloud
|
||||
deleted := 0
|
||||
for _, lp := range localProducts {
|
||||
if _, exists := cloudMap[lp.ID]; !exists {
|
||||
if err := s.repo.DeleteByID("Product", lp.ID); err != nil {
|
||||
log.Printf("ERROR deleting product %d: %v", lp.ID, err)
|
||||
} else {
|
||||
deleted++
|
||||
log.Printf("Deleted local product %d (no longer in cloud)", lp.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Products sync complete: %d created, %d updated, %d deleted, %d failed", created, updated, deleted, failed)
|
||||
}
|
||||
|
||||
func (s *Syncer) syncUsers() {
|
||||
// Optional: logic for background sync of users if needed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user