diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index fd085d7..1cac92c 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -311,6 +311,9 @@ func (s *Server) Start(ctx context.Context) error { ReadHeaderTimeout: 5 * time.Second, } + // Start Stock Reservation Cleanup Worker + s.svc.StartStockCleanupWorker(ctx) + log.Printf("starting %s on %s", s.cfg.AppName, s.cfg.Addr()) return srv.ListenAndServe() } diff --git a/backend/internal/usecase/product_usecase.go b/backend/internal/usecase/product_usecase.go index d527f8c..e8bd742 100644 --- a/backend/internal/usecase/product_usecase.go +++ b/backend/internal/usecase/product_usecase.go @@ -43,6 +43,24 @@ func (s *Service) ListProducts(ctx context.Context, filter domain.ProductFilter, return &domain.ProductPage{Products: products, Total: total, Page: page, PageSize: pageSize}, nil } +// StartStockCleanupWorker runs a background goroutine to expire old reservations. +func (s *Service) StartStockCleanupWorker(ctx context.Context) { + ticker := time.NewTicker(5 * time.Minute) + go func() { + for { + select { + case <-ticker.C: + if err := s.repo.ExpireReservations(context.Background()); err != nil { + fmt.Printf("ERROR: failed to expire stock reservations: %v\n", err) + } + case <-ctx.Done(): + ticker.Stop() + return + } + } + }() +} + // ReserveStock creates a temporary hold on inventory. func (s *Service) ReserveStock(ctx context.Context, productID, inventoryItemID, buyerID uuid.UUID, quantity int64) (*domain.StockReservation, error) { // 1. Check availability (physical stock - active reservations)