From 2d24b4d71b614bfed8f790c5f85968300dd465a7 Mon Sep 17 00:00:00 2001 From: Sidharth Prabhu Date: Fri, 17 Apr 2026 08:25:17 +0530 Subject: [PATCH] backend updated for counter login --- .../sales/controller/ProductController.java | 49 +++++++++++++-- .../controller/StockUpdateController.java | 60 +++++++++++++++++++ .../sales/repository/ProductRepository.java | 3 + frontend/src/pages/Products.tsx | 21 +++++++ 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/com/rit/canteen/sales/controller/StockUpdateController.java diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java index 68a74bcf..332a51ea 100644 --- a/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java +++ b/backend/src/main/java/com/rit/canteen/sales/controller/ProductController.java @@ -19,6 +19,9 @@ public class ProductController { @Autowired private com.rit.canteen.sales.repository.StallRepository stallRepository; + @Autowired + private StockUpdateController stockUpdateController; + @GetMapping public org.springframework.data.domain.Page getAllProducts( @RequestParam(required = false) String search, @@ -32,9 +35,12 @@ public class ProductController { @GetMapping("/category/{categoryName}") public List getProductsByCategory(@PathVariable String categoryName) { - return productRepository.findAll().stream() - .filter(p -> categoryName.equals(p.getCategory())) - .toList(); + return productRepository.findByCategory(categoryName); + } + + @GetMapping("/categories") + public List getAllCategories() { + return productRepository.findDistinctCategories(); } @PostMapping @@ -81,6 +87,9 @@ public class ProductController { Product updated = productRepository.save(product); updateStallAssociations(updated, productDetails.getStalls()); + // Broadcast update + stockUpdateController.broadcastStockUpdate(updated.getId(), updated.getStock()); + return ResponseEntity.ok(productRepository.findById(updated.getId()).orElse(updated)); }) .orElse(ResponseEntity.notFound().build()); @@ -127,8 +136,40 @@ public class ProductController { .map(product -> { Integer currentStock = product.getStock() != null ? product.getStock() : 0; product.setStock(currentStock > 0 ? 0 : 99); // Toggle between Out of Stock and In Stock - return ResponseEntity.ok(productRepository.save(product)); + Product saved = productRepository.save(product); + stockUpdateController.broadcastStockUpdate(saved.getId(), saved.getStock()); + return ResponseEntity.ok(saved); }) .orElse(ResponseEntity.notFound().build()); } + + @PatchMapping("/{id}/stock-adjust") + public ResponseEntity adjustStock(@PathVariable Long id, @RequestParam int delta) { + return productRepository.findById(id) + .map(product -> { + int currentStock = product.getStock() != null ? product.getStock() : 0; + product.setStock(Math.max(0, currentStock + delta)); + Product saved = productRepository.save(product); + stockUpdateController.broadcastStockUpdate(saved.getId(), saved.getStock()); + return ResponseEntity.ok(saved); + }) + .orElse(ResponseEntity.notFound().build()); + } + + @PatchMapping("/batch-assign-category") + @org.springframework.transaction.annotation.Transactional + public ResponseEntity batchAssignCategory(@RequestBody java.util.Map payload) { + String categoryName = (String) payload.get("categoryName"); + List productIds = (List) payload.get("productIds"); + + if (categoryName == null || productIds == null) return ResponseEntity.badRequest().build(); + + for (Integer id : productIds) { + productRepository.findById(id.longValue()).ifPresent(p -> { + p.setCategory(categoryName); + productRepository.save(p); + }); + } + return ResponseEntity.ok().build(); + } } diff --git a/backend/src/main/java/com/rit/canteen/sales/controller/StockUpdateController.java b/backend/src/main/java/com/rit/canteen/sales/controller/StockUpdateController.java new file mode 100644 index 00000000..0e01befc --- /dev/null +++ b/backend/src/main/java/com/rit/canteen/sales/controller/StockUpdateController.java @@ -0,0 +1,60 @@ +package com.rit.canteen.sales.controller; + +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +@RestController +@RequestMapping("/api/stock") +public class StockUpdateController { + + private final List emitters = new CopyOnWriteArrayList<>(); + + @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter streamStockUpdates() { + SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); + this.emitters.add(emitter); + + emitter.onCompletion(() -> this.emitters.remove(emitter)); + emitter.onTimeout(() -> this.emitters.remove(emitter)); + emitter.onError((e) -> this.emitters.remove(emitter)); + + // Send initial heartbeat to keep connection alive + try { + emitter.send(SseEmitter.event() + .name("init") + .data("Connection Established")); + } catch (IOException e) { + this.emitters.remove(emitter); + } + + return emitter; + } + + public void broadcastStockUpdate(Long productId, Integer stock) { + List deadEmitters = new CopyOnWriteArrayList<>(); + Map payload = Map.of( + "productId", productId, + "stock", stock + ); + + this.emitters.forEach(emitter -> { + try { + emitter.send(SseEmitter.event() + .name("stockUpdate") + .data(payload)); + } catch (Exception e) { + deadEmitters.add(emitter); + } + }); + + this.emitters.removeAll(deadEmitters); + } +} diff --git a/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java b/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java index 39bbbf56..4913db1b 100644 --- a/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java +++ b/backend/src/main/java/com/rit/canteen/sales/repository/ProductRepository.java @@ -20,5 +20,8 @@ public interface ProductRepository extends JpaRepository { List findByCategory(String category); + @Query("SELECT DISTINCT p.category FROM Product p WHERE p.category IS NOT NULL") + List findDistinctCategories(); + boolean existsByNameAndCategory(String name, String category); } diff --git a/frontend/src/pages/Products.tsx b/frontend/src/pages/Products.tsx index 24c8f083..007dcccd 100644 --- a/frontend/src/pages/Products.tsx +++ b/frontend/src/pages/Products.tsx @@ -114,6 +114,27 @@ const Products = () => { useEffect(() => { fetchProducts(); + + // SSE Real-time stock updates + const host = window.location.hostname; + const eventSource = new EventSource(`http://${host}:8080/api/stock/stream`); + + eventSource.addEventListener('stockUpdate', (event: any) => { + try { + const update = JSON.parse(event.data); + setProducts(prevProducts => + prevProducts.map(p => + p.id === update.productId ? { ...p, stock: update.stock } : p + ) + ); + } catch (err) { + console.error('Error processing stock update:', err); + } + }); + + return () => { + eventSource.close(); + }; }, [currentPage, pageSize, debouncedSearchTerm]); useEffect(() => {