package com.auction; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; /** * REST API for Auction Monitor control and status. * Provides endpoints for: * - Status checking * - Manual workflow triggers * - Statistics */ @Path("/api/monitor") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class AuctionMonitorResource { private static final Logger LOG = Logger.getLogger(AuctionMonitorResource.class); @Inject DatabaseService db; @Inject QuarkusWorkflowScheduler scheduler; @Inject NotificationService notifier; /** * GET /api/monitor/status * Returns current monitoring status */ @GET @Path("/status") public Response getStatus() { try { Map status = new HashMap<>(); status.put("running", true); status.put("auctions", db.getAllAuctions().size()); status.put("lots", db.getAllLots().size()); status.put("images", db.getImageCount()); // Count closing soon int closingSoon = 0; for (var lot : db.getAllLots()) { if (lot.closingTime() != null && lot.minutesUntilClose() < 30) { closingSoon++; } } status.put("closingSoon", closingSoon); return Response.ok(status).build(); } catch (Exception e) { LOG.error("Failed to get status", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * GET /api/monitor/statistics * Returns detailed statistics */ @GET @Path("/statistics") public Response getStatistics() { try { Map stats = new HashMap<>(); var auctions = db.getAllAuctions(); var lots = db.getAllLots(); stats.put("totalAuctions", auctions.size()); stats.put("totalLots", lots.size()); stats.put("totalImages", db.getImageCount()); // Lot statistics int activeLots = 0; int lotsWithBids = 0; double totalBids = 0; for (var lot : lots) { if (lot.closingTime() != null && lot.minutesUntilClose() > 0) { activeLots++; } if (lot.currentBid() > 0) { lotsWithBids++; totalBids += lot.currentBid(); } } stats.put("activeLots", activeLots); stats.put("lotsWithBids", lotsWithBids); stats.put("totalBidValue", String.format("€%.2f", totalBids)); stats.put("averageBid", lotsWithBids > 0 ? String.format("€%.2f", totalBids / lotsWithBids) : "€0.00"); return Response.ok(stats).build(); } catch (Exception e) { LOG.error("Failed to get statistics", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * POST /api/monitor/trigger/scraper-import * Manually trigger scraper import workflow */ @POST @Path("/trigger/scraper-import") public Response triggerScraperImport() { try { scheduler.importScraperData(); return Response.ok(Map.of("message", "Scraper import triggered successfully")).build(); } catch (Exception e) { LOG.error("Failed to trigger scraper import", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * POST /api/monitor/trigger/image-processing * Manually trigger image processing workflow */ @POST @Path("/trigger/image-processing") public Response triggerImageProcessing() { try { scheduler.processImages(); return Response.ok(Map.of("message", "Image processing triggered successfully")).build(); } catch (Exception e) { LOG.error("Failed to trigger image processing", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * POST /api/monitor/trigger/bid-monitoring * Manually trigger bid monitoring workflow */ @POST @Path("/trigger/bid-monitoring") public Response triggerBidMonitoring() { try { scheduler.monitorBids(); return Response.ok(Map.of("message", "Bid monitoring triggered successfully")).build(); } catch (Exception e) { LOG.error("Failed to trigger bid monitoring", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * POST /api/monitor/trigger/closing-alerts * Manually trigger closing alerts workflow */ @POST @Path("/trigger/closing-alerts") public Response triggerClosingAlerts() { try { scheduler.checkClosingTimes(); return Response.ok(Map.of("message", "Closing alerts triggered successfully")).build(); } catch (Exception e) { LOG.error("Failed to trigger closing alerts", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * GET /api/monitor/auctions * Returns list of all auctions */ @GET @Path("/auctions") public Response getAuctions(@QueryParam("country") String country) { try { var auctions = country != null && !country.isEmpty() ? db.getAuctionsByCountry(country) : db.getAllAuctions(); return Response.ok(auctions).build(); } catch (Exception e) { LOG.error("Failed to get auctions", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * GET /api/monitor/lots * Returns list of active lots */ @GET @Path("/lots") public Response getActiveLots() { try { var lots = db.getActiveLots(); return Response.ok(lots).build(); } catch (Exception e) { LOG.error("Failed to get lots", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * GET /api/monitor/lots/closing-soon * Returns lots closing within specified minutes (default 30) */ @GET @Path("/lots/closing-soon") public Response getLotsClosingSoon(@QueryParam("minutes") @DefaultValue("30") int minutes) { try { var allLots = db.getActiveLots(); var closingSoon = allLots.stream() .filter(lot -> lot.closingTime() != null && lot.minutesUntilClose() < minutes) .toList(); return Response.ok(closingSoon).build(); } catch (Exception e) { LOG.error("Failed to get closing lots", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * GET /api/monitor/lots/{lotId}/images * Returns images for a specific lot */ @GET @Path("/lots/{lotId}/images") public Response getLotImages(@PathParam("lotId") int lotId) { try { var images = db.getImagesForLot(lotId); return Response.ok(images).build(); } catch (Exception e) { LOG.error("Failed to get lot images", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } /** * POST /api/monitor/test-notification * Send a test notification */ @POST @Path("/test-notification") public Response sendTestNotification(Map request) { try { String message = request.getOrDefault("message", "Test notification from Auction Monitor"); String title = request.getOrDefault("title", "Test Notification"); int priority = Integer.parseInt(request.getOrDefault("priority", "0")); notifier.sendNotification(message, title, priority); return Response.ok(Map.of("message", "Test notification sent successfully")).build(); } catch (Exception e) { LOG.error("Failed to send test notification", e); return Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity(Map.of("error", e.getMessage())) .build(); } } }