Fix mock tests
This commit is contained in:
@@ -24,7 +24,7 @@ public class AuctionMonitorProducer {
|
|||||||
@ConfigProperty(name = "auction.database.path") String dbPath) throws SQLException {
|
@ConfigProperty(name = "auction.database.path") String dbPath) throws SQLException {
|
||||||
|
|
||||||
LOG.infof("Initializing DatabaseService with path: %s", dbPath);
|
LOG.infof("Initializing DatabaseService with path: %s", dbPath);
|
||||||
DatabaseService db = new DatabaseService(dbPath);
|
var db = new DatabaseService(dbPath);
|
||||||
db.ensureSchema();
|
db.ensureSchema();
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,343 +20,343 @@ import java.util.Map;
|
|||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
public class AuctionMonitorResource {
|
public class AuctionMonitorResource {
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(AuctionMonitorResource.class);
|
private static final Logger LOG = Logger.getLogger(AuctionMonitorResource.class);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
DatabaseService db;
|
DatabaseService db;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
QuarkusWorkflowScheduler scheduler;
|
QuarkusWorkflowScheduler scheduler;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
NotificationService notifier;
|
NotificationService notifier;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
RateLimitedHttpClient httpClient;
|
RateLimitedHttpClient httpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/monitor/status
|
* GET /api/monitor/status
|
||||||
* Returns current monitoring status
|
* Returns current monitoring status
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/status")
|
@Path("/status")
|
||||||
public Response getStatus() {
|
public Response getStatus() {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> status = new HashMap<>();
|
Map<String, Object> status = new HashMap<>();
|
||||||
status.put("running", true);
|
status.put("running", true);
|
||||||
status.put("auctions", db.getAllAuctions().size());
|
status.put("auctions", db.getAllAuctions().size());
|
||||||
status.put("lots", db.getAllLots().size());
|
status.put("lots", db.getAllLots().size());
|
||||||
status.put("images", db.getImageCount());
|
status.put("images", db.getImageCount());
|
||||||
|
|
||||||
// Count closing soon
|
// Count closing soon
|
||||||
int closingSoon = 0;
|
var closingSoon = 0;
|
||||||
for (var lot : db.getAllLots()) {
|
for (var lot : db.getAllLots()) {
|
||||||
if (lot.closingTime() != null && lot.minutesUntilClose() < 30) {
|
if (lot.closingTime() != null && lot.minutesUntilClose() < 30) {
|
||||||
closingSoon++;
|
closingSoon++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
status.put("closingSoon", closingSoon);
|
}
|
||||||
|
status.put("closingSoon", closingSoon);
|
||||||
return Response.ok(status).build();
|
|
||||||
|
return Response.ok(status).build();
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error("Failed to get status", e);
|
} catch (Exception e) {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
LOG.error("Failed to get status", e);
|
||||||
.entity(Map.of("error", e.getMessage()))
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
.build();
|
.entity(Map.of("error", e.getMessage()))
|
||||||
}
|
.build();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* GET /api/monitor/statistics
|
/**
|
||||||
* Returns detailed statistics
|
* GET /api/monitor/statistics
|
||||||
*/
|
* Returns detailed statistics
|
||||||
@GET
|
*/
|
||||||
@Path("/statistics")
|
@GET
|
||||||
public Response getStatistics() {
|
@Path("/statistics")
|
||||||
try {
|
public Response getStatistics() {
|
||||||
Map<String, Object> stats = new HashMap<>();
|
try {
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
var auctions = db.getAllAuctions();
|
|
||||||
var lots = db.getAllLots();
|
var auctions = db.getAllAuctions();
|
||||||
|
var lots = db.getAllLots();
|
||||||
stats.put("totalAuctions", auctions.size());
|
|
||||||
stats.put("totalLots", lots.size());
|
stats.put("totalAuctions", auctions.size());
|
||||||
stats.put("totalImages", db.getImageCount());
|
stats.put("totalLots", lots.size());
|
||||||
|
stats.put("totalImages", db.getImageCount());
|
||||||
// Lot statistics
|
|
||||||
int activeLots = 0;
|
// Lot statistics
|
||||||
int lotsWithBids = 0;
|
var activeLots = 0;
|
||||||
double totalBids = 0;
|
var lotsWithBids = 0;
|
||||||
|
double totalBids = 0;
|
||||||
for (var lot : lots) {
|
|
||||||
if (lot.closingTime() != null && lot.minutesUntilClose() > 0) {
|
for (var lot : lots) {
|
||||||
activeLots++;
|
if (lot.closingTime() != null && lot.minutesUntilClose() > 0) {
|
||||||
}
|
activeLots++;
|
||||||
if (lot.currentBid() > 0) {
|
|
||||||
lotsWithBids++;
|
|
||||||
totalBids += lot.currentBid();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (lot.currentBid() > 0) {
|
||||||
stats.put("activeLots", activeLots);
|
lotsWithBids++;
|
||||||
stats.put("lotsWithBids", lotsWithBids);
|
totalBids += lot.currentBid();
|
||||||
stats.put("totalBidValue", String.format("€%.2f", totalBids));
|
}
|
||||||
stats.put("averageBid", lotsWithBids > 0 ? String.format("€%.2f", totalBids / lotsWithBids) : "€0.00");
|
}
|
||||||
|
|
||||||
return Response.ok(stats).build();
|
stats.put("activeLots", activeLots);
|
||||||
|
stats.put("lotsWithBids", lotsWithBids);
|
||||||
} catch (Exception e) {
|
stats.put("totalBidValue", String.format("€%.2f", totalBids));
|
||||||
LOG.error("Failed to get statistics", e);
|
stats.put("averageBid", lotsWithBids > 0 ? String.format("€%.2f", totalBids / lotsWithBids) : "€0.00");
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
|
||||||
.entity(Map.of("error", e.getMessage()))
|
return Response.ok(stats).build();
|
||||||
.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()))
|
||||||
* POST /api/monitor/trigger/scraper-import
|
.build();
|
||||||
* Manually trigger scraper import workflow
|
}
|
||||||
*/
|
}
|
||||||
@POST
|
|
||||||
@Path("/trigger/scraper-import")
|
/**
|
||||||
public Response triggerScraperImport() {
|
* POST /api/monitor/trigger/scraper-import
|
||||||
try {
|
* Manually trigger scraper import workflow
|
||||||
scheduler.importScraperData();
|
*/
|
||||||
return Response.ok(Map.of("message", "Scraper import triggered successfully")).build();
|
@POST
|
||||||
} catch (Exception e) {
|
@Path("/trigger/scraper-import")
|
||||||
LOG.error("Failed to trigger scraper import", e);
|
public Response triggerScraperImport() {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
scheduler.importScraperData();
|
||||||
.build();
|
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()))
|
||||||
* POST /api/monitor/trigger/image-processing
|
.build();
|
||||||
* Manually trigger image processing workflow
|
}
|
||||||
*/
|
}
|
||||||
@POST
|
|
||||||
@Path("/trigger/image-processing")
|
/**
|
||||||
public Response triggerImageProcessing() {
|
* POST /api/monitor/trigger/image-processing
|
||||||
try {
|
* Manually trigger image processing workflow
|
||||||
scheduler.processImages();
|
*/
|
||||||
return Response.ok(Map.of("message", "Image processing triggered successfully")).build();
|
@POST
|
||||||
} catch (Exception e) {
|
@Path("/trigger/image-processing")
|
||||||
LOG.error("Failed to trigger image processing", e);
|
public Response triggerImageProcessing() {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
scheduler.processImages();
|
||||||
.build();
|
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()))
|
||||||
* POST /api/monitor/trigger/bid-monitoring
|
.build();
|
||||||
* Manually trigger bid monitoring workflow
|
}
|
||||||
*/
|
}
|
||||||
@POST
|
|
||||||
@Path("/trigger/bid-monitoring")
|
/**
|
||||||
public Response triggerBidMonitoring() {
|
* POST /api/monitor/trigger/bid-monitoring
|
||||||
try {
|
* Manually trigger bid monitoring workflow
|
||||||
scheduler.monitorBids();
|
*/
|
||||||
return Response.ok(Map.of("message", "Bid monitoring triggered successfully")).build();
|
@POST
|
||||||
} catch (Exception e) {
|
@Path("/trigger/bid-monitoring")
|
||||||
LOG.error("Failed to trigger bid monitoring", e);
|
public Response triggerBidMonitoring() {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
scheduler.monitorBids();
|
||||||
.build();
|
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()))
|
||||||
* POST /api/monitor/trigger/closing-alerts
|
.build();
|
||||||
* Manually trigger closing alerts workflow
|
}
|
||||||
*/
|
}
|
||||||
@POST
|
|
||||||
@Path("/trigger/closing-alerts")
|
/**
|
||||||
public Response triggerClosingAlerts() {
|
* POST /api/monitor/trigger/closing-alerts
|
||||||
try {
|
* Manually trigger closing alerts workflow
|
||||||
scheduler.checkClosingTimes();
|
*/
|
||||||
return Response.ok(Map.of("message", "Closing alerts triggered successfully")).build();
|
@POST
|
||||||
} catch (Exception e) {
|
@Path("/trigger/closing-alerts")
|
||||||
LOG.error("Failed to trigger closing alerts", e);
|
public Response triggerClosingAlerts() {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
scheduler.checkClosingTimes();
|
||||||
.build();
|
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()))
|
||||||
* GET /api/monitor/auctions
|
.build();
|
||||||
* Returns list of all auctions
|
}
|
||||||
*/
|
}
|
||||||
@GET
|
|
||||||
@Path("/auctions")
|
/**
|
||||||
public Response getAuctions(@QueryParam("country") String country) {
|
* GET /api/monitor/auctions
|
||||||
try {
|
* Returns list of all auctions
|
||||||
var auctions = country != null && !country.isEmpty()
|
*/
|
||||||
? db.getAuctionsByCountry(country)
|
@GET
|
||||||
: db.getAllAuctions();
|
@Path("/auctions")
|
||||||
|
public Response getAuctions(@QueryParam("country") String country) {
|
||||||
return Response.ok(auctions).build();
|
try {
|
||||||
} catch (Exception e) {
|
var auctions = country != null && !country.isEmpty()
|
||||||
LOG.error("Failed to get auctions", e);
|
? db.getAuctionsByCountry(country)
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
: db.getAllAuctions();
|
||||||
.entity(Map.of("error", e.getMessage()))
|
|
||||||
.build();
|
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()))
|
||||||
* GET /api/monitor/lots
|
.build();
|
||||||
* Returns list of active lots
|
}
|
||||||
*/
|
}
|
||||||
@GET
|
|
||||||
@Path("/lots")
|
/**
|
||||||
public Response getActiveLots() {
|
* GET /api/monitor/lots
|
||||||
try {
|
* Returns list of active lots
|
||||||
var lots = db.getActiveLots();
|
*/
|
||||||
return Response.ok(lots).build();
|
@GET
|
||||||
} catch (Exception e) {
|
@Path("/lots")
|
||||||
LOG.error("Failed to get lots", e);
|
public Response getActiveLots() {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
var lots = db.getActiveLots();
|
||||||
.build();
|
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()))
|
||||||
* GET /api/monitor/lots/closing-soon
|
.build();
|
||||||
* Returns lots closing within specified minutes (default 30)
|
}
|
||||||
*/
|
}
|
||||||
@GET
|
|
||||||
@Path("/lots/closing-soon")
|
/**
|
||||||
public Response getLotsClosingSoon(@QueryParam("minutes") @DefaultValue("30") int minutes) {
|
* GET /api/monitor/lots/closing-soon
|
||||||
try {
|
* Returns lots closing within specified minutes (default 30)
|
||||||
var allLots = db.getActiveLots();
|
*/
|
||||||
var closingSoon = allLots.stream()
|
@GET
|
||||||
.filter(lot -> lot.closingTime() != null && lot.minutesUntilClose() < minutes)
|
@Path("/lots/closing-soon")
|
||||||
.toList();
|
public Response getLotsClosingSoon(@QueryParam("minutes") @DefaultValue("30") int minutes) {
|
||||||
|
try {
|
||||||
return Response.ok(closingSoon).build();
|
var allLots = db.getActiveLots();
|
||||||
} catch (Exception e) {
|
var closingSoon = allLots.stream()
|
||||||
LOG.error("Failed to get closing lots", e);
|
.filter(lot -> lot.closingTime() != null && lot.minutesUntilClose() < minutes)
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
.toList();
|
||||||
.entity(Map.of("error", e.getMessage()))
|
|
||||||
.build();
|
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()))
|
||||||
* GET /api/monitor/lots/{lotId}/images
|
.build();
|
||||||
* Returns images for a specific lot
|
}
|
||||||
*/
|
}
|
||||||
@GET
|
|
||||||
@Path("/lots/{lotId}/images")
|
/**
|
||||||
public Response getLotImages(@PathParam("lotId") int lotId) {
|
* GET /api/monitor/lots/{lotId}/images
|
||||||
try {
|
* Returns images for a specific lot
|
||||||
var images = db.getImagesForLot(lotId);
|
*/
|
||||||
return Response.ok(images).build();
|
@GET
|
||||||
} catch (Exception e) {
|
@Path("/lots/{lotId}/images")
|
||||||
LOG.error("Failed to get lot images", e);
|
public Response getLotImages(@PathParam("lotId") int lotId) {
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
try {
|
||||||
.entity(Map.of("error", e.getMessage()))
|
var images = db.getImagesForLot(lotId);
|
||||||
.build();
|
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()))
|
||||||
* POST /api/monitor/test-notification
|
.build();
|
||||||
* Send a test notification
|
}
|
||||||
*/
|
}
|
||||||
@POST
|
|
||||||
@Path("/test-notification")
|
/**
|
||||||
public Response sendTestNotification(Map<String, String> request) {
|
* POST /api/monitor/test-notification
|
||||||
try {
|
* Send a test notification
|
||||||
String message = request.getOrDefault("message", "Test notification from Auction Monitor");
|
*/
|
||||||
String title = request.getOrDefault("title", "Test Notification");
|
@POST
|
||||||
int priority = Integer.parseInt(request.getOrDefault("priority", "0"));
|
@Path("/test-notification")
|
||||||
|
public Response sendTestNotification(Map<String, String> request) {
|
||||||
notifier.sendNotification(message, title, priority);
|
try {
|
||||||
|
var message = request.getOrDefault("message", "Test notification from Auction Monitor");
|
||||||
return Response.ok(Map.of("message", "Test notification sent successfully")).build();
|
var title = request.getOrDefault("title", "Test Notification");
|
||||||
} catch (Exception e) {
|
var priority = Integer.parseInt(request.getOrDefault("priority", "0"));
|
||||||
LOG.error("Failed to send test notification", e);
|
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
notifier.sendNotification(message, title, priority);
|
||||||
.entity(Map.of("error", e.getMessage()))
|
|
||||||
.build();
|
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()))
|
||||||
* GET /api/monitor/rate-limit/stats
|
.build();
|
||||||
* Returns HTTP rate limiting statistics for all hosts
|
}
|
||||||
*/
|
}
|
||||||
@GET
|
|
||||||
@Path("/rate-limit/stats")
|
/**
|
||||||
public Response getRateLimitStats() {
|
* GET /api/monitor/rate-limit/stats
|
||||||
try {
|
* Returns HTTP rate limiting statistics for all hosts
|
||||||
var stats = httpClient.getAllStats();
|
*/
|
||||||
|
@GET
|
||||||
Map<String, Object> response = new HashMap<>();
|
@Path("/rate-limit/stats")
|
||||||
response.put("hosts", stats.size());
|
public Response getRateLimitStats() {
|
||||||
|
try {
|
||||||
Map<String, Object> hostStats = new HashMap<>();
|
var stats = httpClient.getAllStats();
|
||||||
for (var entry : stats.entrySet()) {
|
|
||||||
var stat = entry.getValue();
|
Map<String, Object> response = new HashMap<>();
|
||||||
hostStats.put(entry.getKey(), Map.of(
|
response.put("hosts", stats.size());
|
||||||
|
|
||||||
|
Map<String, Object> hostStats = new HashMap<>();
|
||||||
|
for (var entry : stats.entrySet()) {
|
||||||
|
var stat = entry.getValue();
|
||||||
|
hostStats.put(entry.getKey(), Map.of(
|
||||||
"totalRequests", stat.getTotalRequests(),
|
"totalRequests", stat.getTotalRequests(),
|
||||||
"successfulRequests", stat.getSuccessfulRequests(),
|
"successfulRequests", stat.getSuccessfulRequests(),
|
||||||
"failedRequests", stat.getFailedRequests(),
|
"failedRequests", stat.getFailedRequests(),
|
||||||
"rateLimitedRequests", stat.getRateLimitedRequests(),
|
"rateLimitedRequests", stat.getRateLimitedRequests(),
|
||||||
"averageDurationMs", stat.getAverageDurationMs()
|
"averageDurationMs", stat.getAverageDurationMs()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
response.put("statistics", hostStats);
|
response.put("statistics", hostStats);
|
||||||
|
|
||||||
return Response.ok(response).build();
|
return Response.ok(response).build();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to get rate limit stats", e);
|
LOG.error("Failed to get rate limit stats", e);
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
.entity(Map.of("error", e.getMessage()))
|
.entity(Map.of("error", e.getMessage()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /api/monitor/rate-limit/stats/{host}
|
* GET /api/monitor/rate-limit/stats/{host}
|
||||||
* Returns HTTP rate limiting statistics for a specific host
|
* Returns HTTP rate limiting statistics for a specific host
|
||||||
*/
|
*/
|
||||||
@GET
|
@GET
|
||||||
@Path("/rate-limit/stats/{host}")
|
@Path("/rate-limit/stats/{host}")
|
||||||
public Response getRateLimitStatsForHost(@PathParam("host") String host) {
|
public Response getRateLimitStatsForHost(@PathParam("host") String host) {
|
||||||
try {
|
try {
|
||||||
var stat = httpClient.getStats(host);
|
var stat = httpClient.getStats(host);
|
||||||
|
|
||||||
if (stat == null) {
|
if (stat == null) {
|
||||||
return Response.status(Response.Status.NOT_FOUND)
|
return Response.status(Response.Status.NOT_FOUND)
|
||||||
.entity(Map.of("error", "No statistics found for host: " + host))
|
.entity(Map.of("error", "No statistics found for host: " + host))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> response = Map.of(
|
Map<String, Object> response = Map.of(
|
||||||
"host", stat.getHost(),
|
"host", stat.getHost(),
|
||||||
"totalRequests", stat.getTotalRequests(),
|
"totalRequests", stat.getTotalRequests(),
|
||||||
"successfulRequests", stat.getSuccessfulRequests(),
|
"successfulRequests", stat.getSuccessfulRequests(),
|
||||||
"failedRequests", stat.getFailedRequests(),
|
"failedRequests", stat.getFailedRequests(),
|
||||||
"rateLimitedRequests", stat.getRateLimitedRequests(),
|
"rateLimitedRequests", stat.getRateLimitedRequests(),
|
||||||
"averageDurationMs", stat.getAverageDurationMs()
|
"averageDurationMs", stat.getAverageDurationMs()
|
||||||
);
|
);
|
||||||
|
|
||||||
return Response.ok(response).build();
|
return Response.ok(response).build();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to get rate limit stats for host", e);
|
LOG.error("Failed to get rate limit stats for host", e);
|
||||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
.entity(Map.of("error", e.getMessage()))
|
.entity(Map.of("error", e.getMessage()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package auctiora;
|
package auctiora;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
|
||||||
import java.io.Console;
|
import java.io.Console;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
@@ -16,9 +20,9 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class DatabaseService {
|
public class DatabaseService {
|
||||||
|
|
||||||
private final String url;
|
private final String url;
|
||||||
|
|
||||||
DatabaseService(String dbPath) {
|
DatabaseService(String dbPath) {
|
||||||
this.url = "jdbc:sqlite:" + dbPath;
|
this.url = "jdbc:sqlite:" + dbPath;
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ public class DatabaseService {
|
|||||||
closing_time TEXT,
|
closing_time TEXT,
|
||||||
discovered_at INTEGER
|
discovered_at INTEGER
|
||||||
)""");
|
)""");
|
||||||
|
|
||||||
// Lots table (populated by external scraper)
|
// Lots table (populated by external scraper)
|
||||||
stmt.execute("""
|
stmt.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS lots (
|
CREATE TABLE IF NOT EXISTS lots (
|
||||||
@@ -62,7 +66,7 @@ public class DatabaseService {
|
|||||||
closing_notified INTEGER DEFAULT 0,
|
closing_notified INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (sale_id) REFERENCES auctions(auction_id)
|
FOREIGN KEY (sale_id) REFERENCES auctions(auction_id)
|
||||||
)""");
|
)""");
|
||||||
|
|
||||||
// Images table (populated by this process)
|
// Images table (populated by this process)
|
||||||
stmt.execute("""
|
stmt.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS images (
|
CREATE TABLE IF NOT EXISTS images (
|
||||||
@@ -74,13 +78,40 @@ public class DatabaseService {
|
|||||||
processed_at INTEGER,
|
processed_at INTEGER,
|
||||||
FOREIGN KEY (lot_id) REFERENCES lots(lot_id)
|
FOREIGN KEY (lot_id) REFERENCES lots(lot_id)
|
||||||
)""");
|
)""");
|
||||||
|
|
||||||
|
// Migrate existing tables to add missing columns
|
||||||
|
migrateSchema(stmt);
|
||||||
|
|
||||||
// Indexes for performance
|
// Indexes for performance
|
||||||
stmt.execute("CREATE INDEX IF NOT EXISTS idx_auctions_country ON auctions(country)");
|
stmt.execute("CREATE INDEX IF NOT EXISTS idx_auctions_country ON auctions(country)");
|
||||||
stmt.execute("CREATE INDEX IF NOT EXISTS idx_lots_sale_id ON lots(sale_id)");
|
stmt.execute("CREATE INDEX IF NOT EXISTS idx_lots_sale_id ON lots(sale_id)");
|
||||||
stmt.execute("CREATE INDEX IF NOT EXISTS idx_images_lot_id ON images(lot_id)");
|
stmt.execute("CREATE INDEX IF NOT EXISTS idx_images_lot_id ON images(lot_id)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrates existing database schema to add new columns.
|
||||||
|
* SQLite doesn't support DROP COLUMN, so we add columns with ALTER TABLE ADD COLUMN.
|
||||||
|
*/
|
||||||
|
private void migrateSchema(java.sql.Statement stmt) throws SQLException {
|
||||||
|
// Check if country column exists in auctions table
|
||||||
|
try (var rs = stmt.executeQuery("PRAGMA table_info(auctions)")) {
|
||||||
|
boolean hasCountry = false;
|
||||||
|
while (rs.next()) {
|
||||||
|
if ("country".equals(rs.getString("name"))) {
|
||||||
|
hasCountry = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasCountry) {
|
||||||
|
log.info("Migrating schema: Adding 'country' column to auctions table");
|
||||||
|
stmt.execute("ALTER TABLE auctions ADD COLUMN country TEXT");
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// Table might not exist yet, which is fine
|
||||||
|
log.debug("Could not check auctions table schema: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts or updates an auction record (typically called by external scraper)
|
* Inserts or updates an auction record (typically called by external scraper)
|
||||||
@@ -355,7 +386,7 @@ public class DatabaseService {
|
|||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
// Table might not exist in scraper format - that's ok
|
// Table might not exist in scraper format - that's ok
|
||||||
IO.println("ℹ️ Scraper auction table not found or incompatible schema");
|
log.info("ℹ️ Scraper auction table not found or incompatible schema");
|
||||||
}
|
}
|
||||||
|
|
||||||
return imported;
|
return imported;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package auctiora;
|
package auctiora;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -16,11 +19,11 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
class ImageProcessingService {
|
class ImageProcessingService {
|
||||||
|
|
||||||
private final RateLimitedHttpClient httpClient;
|
private final RateLimitedHttpClient httpClient;
|
||||||
private final DatabaseService db;
|
private final DatabaseService db;
|
||||||
private final ObjectDetectionService detector;
|
private final ObjectDetectionService detector;
|
||||||
|
|
||||||
ImageProcessingService(DatabaseService db, ObjectDetectionService detector, RateLimitedHttpClient httpClient) {
|
ImageProcessingService(DatabaseService db, ObjectDetectionService detector, RateLimitedHttpClient httpClient) {
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package auctiora;
|
package auctiora;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
|
||||||
import javax.mail.*;
|
import javax.mail.*;
|
||||||
import javax.mail.internet.*;
|
import javax.mail.internet.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -9,9 +13,9 @@ import java.util.Properties;
|
|||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class NotificationService {
|
public class NotificationService {
|
||||||
|
|
||||||
private final Config config;
|
private final Config config;
|
||||||
|
|
||||||
public NotificationService(String cfg) {
|
public NotificationService(String cfg) {
|
||||||
this.config = Config.parse(cfg);
|
this.config = Config.parse(cfg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package auctiora;
|
package auctiora;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.opencv.core.Mat;
|
import org.opencv.core.Mat;
|
||||||
import org.opencv.core.Scalar;
|
import org.opencv.core.Scalar;
|
||||||
@@ -29,11 +33,11 @@ import static org.opencv.dnn.Dnn.DNN_TARGET_CPU;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ObjectDetectionService {
|
public class ObjectDetectionService {
|
||||||
|
|
||||||
private final Net net;
|
private final Net net;
|
||||||
private final List<String> classNames;
|
private final List<String> classNames;
|
||||||
private final boolean enabled;
|
private final boolean enabled;
|
||||||
|
|
||||||
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
|
ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException {
|
||||||
// Check if model files exist
|
// Check if model files exist
|
||||||
var cfgFile = Paths.get(cfgPath);
|
var cfgFile = Paths.get(cfgPath);
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ class IntegrationTest {
|
|||||||
"non_existent.weights",
|
"non_existent.weights",
|
||||||
"non_existent.txt"
|
"non_existent.txt"
|
||||||
);
|
);
|
||||||
|
|
||||||
RateLimitedHttpClient httpClient = new RateLimitedHttpClient();
|
var httpClient = new RateLimitedHttpClient();
|
||||||
imageProcessor = new ImageProcessingService(db, detector, httpClient);
|
imageProcessor = new ImageProcessingService(db, detector, httpClient);
|
||||||
|
|
||||||
monitor = new TroostwijkMonitor(
|
monitor = new TroostwijkMonitor(
|
||||||
@@ -170,8 +170,8 @@ class IntegrationTest {
|
|||||||
db.updateLotCurrentBid(updatedLot);
|
db.updateLotCurrentBid(updatedLot);
|
||||||
|
|
||||||
// Send notification
|
// Send notification
|
||||||
String message = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)",
|
var message = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)",
|
||||||
lot.lotId(), 2000.00, 1500.00);
|
lot.lotId(), 2000.00, 1500.00);
|
||||||
|
|
||||||
assertDoesNotThrow(() ->
|
assertDoesNotThrow(() ->
|
||||||
notifier.sendNotification(message, "Kavel bieding update", 0)
|
notifier.sendNotification(message, "Kavel bieding update", 0)
|
||||||
@@ -212,7 +212,7 @@ class IntegrationTest {
|
|||||||
assertTrue(closingSoon.minutesUntilClose() < 5);
|
assertTrue(closingSoon.minutesUntilClose() < 5);
|
||||||
|
|
||||||
// Send high-priority notification
|
// Send high-priority notification
|
||||||
String message = "Kavel " + closingSoon.lotId() + " sluit binnen 5 min.";
|
var message = "Kavel " + closingSoon.lotId() + " sluit binnen 5 min.";
|
||||||
assertDoesNotThrow(() ->
|
assertDoesNotThrow(() ->
|
||||||
notifier.sendNotification(message, "Lot nearing closure", 1)
|
notifier.sendNotification(message, "Lot nearing closure", 1)
|
||||||
);
|
);
|
||||||
@@ -281,7 +281,7 @@ class IntegrationTest {
|
|||||||
assertDoesNotThrow(() -> monitor.processPendingImages());
|
assertDoesNotThrow(() -> monitor.processPendingImages());
|
||||||
|
|
||||||
// Verify database integrity
|
// Verify database integrity
|
||||||
int imageCount = db.getImageCount();
|
var imageCount = db.getImageCount();
|
||||||
assertTrue(imageCount >= 0);
|
assertTrue(imageCount >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,11 +348,11 @@ class IntegrationTest {
|
|||||||
assertTrue(allLabels.contains("excavator") || allLabels.contains("machinery"));
|
assertTrue(allLabels.contains("excavator") || allLabels.contains("machinery"));
|
||||||
|
|
||||||
// Simulate value estimation notification
|
// Simulate value estimation notification
|
||||||
String message = String.format(
|
var message = String.format(
|
||||||
"Lot contains: %s\nEstimated value: €%,.2f",
|
"Lot contains: %s\nEstimated value: €%,.2f",
|
||||||
String.join(", ", allLabels),
|
String.join(", ", allLabels),
|
||||||
lot.currentBid()
|
lot.currentBid()
|
||||||
);
|
);
|
||||||
|
|
||||||
assertDoesNotThrow(() ->
|
assertDoesNotThrow(() ->
|
||||||
notifier.sendNotification(message, "Object Detected", 0)
|
notifier.sendNotification(message, "Object Detected", 0)
|
||||||
@@ -363,9 +363,9 @@ class IntegrationTest {
|
|||||||
@Order(9)
|
@Order(9)
|
||||||
@DisplayName("Integration: Handle rapid concurrent updates")
|
@DisplayName("Integration: Handle rapid concurrent updates")
|
||||||
void testConcurrentOperations() throws InterruptedException, SQLException {
|
void testConcurrentOperations() throws InterruptedException, SQLException {
|
||||||
Thread auctionThread = new Thread(() -> {
|
var auctionThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
db.upsertAuction(new AuctionInfo(
|
db.upsertAuction(new AuctionInfo(
|
||||||
60000 + i, "Concurrent Auction " + i, "Test, NL", "Test", "NL",
|
60000 + i, "Concurrent Auction " + i, "Test, NL", "Test", "NL",
|
||||||
"https://example.com/60" + i, "A1", 5, null
|
"https://example.com/60" + i, "A1", 5, null
|
||||||
@@ -375,10 +375,10 @@ class IntegrationTest {
|
|||||||
fail("Auction thread failed: " + e.getMessage());
|
fail("Auction thread failed: " + e.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Thread lotThread = new Thread(() -> {
|
var lotThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < 10; i++) {
|
for (var i = 0; i < 10; i++) {
|
||||||
db.upsertLot(new Lot(
|
db.upsertLot(new Lot(
|
||||||
60000 + i, 70000 + i, "Concurrent Lot " + i, "Desc", "", "", 0, "Cat",
|
60000 + i, 70000 + i, "Concurrent Lot " + i, "Desc", "", "", 0, "Cat",
|
||||||
100.0 * i, "EUR", "https://example.com/70" + i, null, false
|
100.0 * i, "EUR", "https://example.com/70" + i, null, false
|
||||||
@@ -397,14 +397,14 @@ class IntegrationTest {
|
|||||||
// Verify all were inserted
|
// Verify all were inserted
|
||||||
var auctions = db.getAllAuctions();
|
var auctions = db.getAllAuctions();
|
||||||
var lots = db.getAllLots();
|
var lots = db.getAllLots();
|
||||||
|
|
||||||
long auctionCount = auctions.stream()
|
var auctionCount = auctions.stream()
|
||||||
.filter(a -> a.auctionId() >= 60000 && a.auctionId() < 60010)
|
.filter(a -> a.auctionId() >= 60000 && a.auctionId() < 60010)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
long lotCount = lots.stream()
|
var lotCount = lots.stream()
|
||||||
.filter(l -> l.lotId() >= 70000 && l.lotId() < 70010)
|
.filter(l -> l.lotId() >= 70000 && l.lotId() < 70010)
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
assertEquals(10, auctionCount);
|
assertEquals(10, auctionCount);
|
||||||
assertEquals(10, lotCount);
|
assertEquals(10, lotCount);
|
||||||
|
|||||||
Reference in New Issue
Block a user