Compare commits

...

124 Commits

Author SHA1 Message Date
Tour
8860340cc4 fix-tests-cleanup 2025-12-08 08:44:09 +01:00
Tour
553a61559c fix-tests-cleanup 2025-12-08 08:36:15 +01:00
Tour
5440ed0ace all 2025-12-08 08:16:35 +01:00
Tour
35851c124c all 2025-12-08 08:14:42 +01:00
Tour
3cc0d40fa3 fix-tests-cleanup 2025-12-08 07:52:54 +01:00
Tour
be65f4a5e6 fix-tests-cleanup 2025-12-08 07:19:50 +01:00
Tour
3358a2693c fix-tests-cleanup 2025-12-08 05:37:35 +01:00
Tour
62cda5c0cb fix-tests-cleanup 2025-12-08 05:37:29 +01:00
Tour
7600cebcbb slash 2025-12-07 18:06:17 +01:00
Tour
394469923b Features 2025-12-07 16:25:29 +01:00
Tour
2da6049206 test 2025-12-07 15:51:11 +01:00
Tour
3cf2d2ef7a redeploy 2025-12-07 15:27:29 +01:00
Tour
afd7b311a9 Enrich ALL lots on startup in background thread 2025-12-07 14:48:36 +01:00
Tour
7ab21ae840 Add startup enrichment trigger for lot intelligence data
- Added StartupEvent observer to QuarkusWorkflowScheduler
- Triggers enrichment of lots closing within 24 hours on startup
- Ensures bid intelligence data is populated immediately after deployment
- Fixes issue where server showed 0 lots with bids

This ensures the GraphQL enrichment service runs at startup to populate
bid_count, starting_bid, followers_count and other intelligence fields.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 14:43:10 +01:00
Tour
12c3a732e4 Fix database schema: Change auction_id and lot_id from BIGINT to TEXT
The scraper uses TEXT IDs like "A7-40063-2" but DatabaseService was creating
BIGINT columns, causing PRIMARY KEY constraint failures on the server.

Changes:
- auction_id: BIGINT -> TEXT PRIMARY KEY
- lot_id: BIGINT -> TEXT PRIMARY KEY
- sale_id: BIGINT -> TEXT
- Added UNIQUE constraints on URLs
- Added migration script (fix-schema.sql)

This fixes the "UNIQUE constraint failed: auctions.auction_id" errors
and allows bid data to populate correctly on the server.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 13:59:26 +01:00
Tour
4d7da94315 Fix GraphQL enrichment to use displayId instead of numeric lotId
- Added displayId (String) field to Lot record for full lot ID (e.g., "A1-34732-49")
- Updated ScraperDataAdapter to extract both numeric ID and displayId from database
- Fixed TroostwijkGraphQLClient to query by displayId using lotDetails() instead of lot()
- Matched Python scraper's query structure with LOT_BIDDING_QUERY pattern
- Updated GraphQL response parsing to handle lotDetails.location and biddingStatistics
- Added upsertLotWithIntelligence() method to DatabaseService for full intelligence updates
- Updated LotEnrichmentService to pass displayId to GraphQL client

This fixes the "No intelligence data returned" error on production server.
GraphQL API requires string displayId parameter, not numeric lot ID.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 13:31:40 +01:00
Tour
80b9841aee Fix PRIMARY KEY constraint handling in auction import
- Handle both auction_id and URL constraint failures
- Add fallback UPDATE by URL when INSERT fails
- Properly catch and log constraint violations without propagating
- Fixes import errors on server with duplicate auctions
2025-12-07 13:10:39 +01:00
Tour
00eb3f7aca goog 2025-12-07 12:59:22 +01:00
Tour
825058f790 goog 2025-12-07 12:56:53 +01:00
Tour
d5d245cfc1 goog 2025-12-07 12:47:36 +01:00
Tour
ca19649b6a Features 2025-12-07 11:41:22 +01:00
Tour
65bb5cd80a Features 2025-12-07 11:31:55 +01:00
Tour
43b5fc03fd Fix mock tests 2025-12-07 11:08:59 +01:00
Tour
11a76e0292 Fix mock tests 2025-12-07 09:59:08 +01:00
Tour
a649b629e4 Fix mock tests 2025-12-07 06:51:18 +01:00
Tour
3efa83bc44 Fix mock tests 2025-12-07 06:32:03 +01:00
Tour
ef804b3896 Fix mock tests 2025-12-07 06:28:37 +01:00
Tour
f561a73b01 Fix mock tests 2025-12-07 02:36:00 +01:00
Tour
432fcbc503 Fix mock tests 2025-12-06 21:37:08 +01:00
Tour
b4e0f8c13b Fix mock tests 2025-12-06 21:36:55 +01:00
Tour
e216a763ac Fix build: Update tests for image download refactor
- Remove RateLimitedHttpClient from ImageProcessingService constructor
- Rewrite ImageProcessingServiceTest to test new object detection workflow
- Fix IntegrationTest constructor call
- Add insertImage() back to DatabaseService for test compatibility
- Tests now focus on object detection rather than image downloading
2025-12-06 21:34:29 +01:00
Tour
6091b7180f go 2025-12-06 21:27:19 +01:00
Tour
288ee6a2a6 test 2025-12-06 07:08:07 +01:00
Tour
a25c0bdf5d %* 2025-12-06 07:04:53 +01:00
Tour
4ecb6625c8 all 2025-12-06 06:23:34 +01:00
Tour
174d0b136e all 2025-12-06 06:19:23 +01:00
Tour
d8f7464944 front-end-fix 2025-12-06 06:14:26 +01:00
Tour
9f5003ecc5 front-end-fix 2025-12-06 06:03:43 +01:00
Tour
cda9b648ad front-end-fix 2025-12-06 05:59:22 +01:00
Tour
1af565ae1b front-end-fix 2025-12-06 05:51:55 +01:00
Tour
36b03dea7b Fix mock tests 2025-12-06 05:41:14 +01:00
Tour
528a217708 front-end-fix 2025-12-06 05:39:59 +01:00
Tour
d1a149e40d front-end-fix 2025-12-06 05:29:41 +01:00
Tour
758e60ecb3 Fix mock tests 2025-12-05 21:10:17 +01:00
Tour
e06f5747ec done 2025-12-05 20:58:32 +01:00
Tour
0b1be38681 done 2025-12-05 20:54:23 +01:00
Tour
e9b4298f58 init 2025-12-05 09:42:48 +01:00
Tour
887295260f dns 2025-12-05 09:35:17 +01:00
Tour
a06434642c dns 2025-12-05 08:59:23 +01:00
Tour
36a1edfecf dns 2025-12-05 08:57:29 +01:00
Tour
243573d4b2 all 2025-12-05 08:49:59 +01:00
Tour
41de6c1e8a all 2025-12-05 08:35:19 +01:00
Tour
0ab9430f35 all 2025-12-05 08:27:43 +01:00
Tour
20c2129d06 update 2025-12-05 08:20:18 +01:00
Tour
5430610b56 update 2025-12-05 08:14:15 +01:00
Tour
c91b2d7f3a update 2025-12-05 07:57:58 +01:00
Tour
3357267581 update 2025-12-05 07:42:17 +01:00
Tour
c6263e78b2 update 2025-12-05 07:40:26 +01:00
Tour
cf486796ac update 2025-12-05 07:36:25 +01:00
Tour
79f63ba9a5 update 2025-12-05 07:35:20 +01:00
Tour
fb5fa1b0ff update 2025-12-05 07:28:49 +01:00
Tour
6325d07909 monitor-page 2025-12-05 07:06:34 +01:00
Tour
04df491d64 Fix mock tests 2025-12-05 06:19:28 +01:00
Tour
f05a8b73ec Fix mock tests 2025-12-05 04:48:41 +01:00
Tour
1292d09427 Fix mock tests 2025-12-05 03:44:31 +01:00
Tour
5083a68205 Fix mock tests 2025-12-05 03:44:28 +01:00
Tour
8ecd9fcbda Fix mock tests 2025-12-05 03:42:36 +01:00
Tour
ff8f5f2c1a Fix mock tests 2025-12-04 20:14:28 +01:00
Tour
2ff6fcca17 Fix mock tests 2025-12-04 20:07:54 +01:00
Tour
d52bd8f94e Fix mock tests 2025-12-04 20:00:33 +01:00
Tour
ed74bb5e93 Fix mock tests 2025-12-04 19:38:38 +01:00
Tour
9857f053a1 Fix mock tests 2025-12-04 04:30:44 +01:00
Tour
e71d52be8a USe ASM 9.8 with Java 25 2025-12-04 04:00:03 +01:00
Tour
cad27f1842 USe ASM 9.8 with Java 25 2025-12-04 03:31:27 +01:00
Tour
d2000a46bc USe ASM 9.8 with Java 25 2025-12-04 03:21:03 +01:00
Tour
8e06e20b70 start 2025-12-03 19:03:03 +01:00
Tour
4c32043e5f start 2025-12-03 17:30:09 +01:00
Tour
8fff75dcf2 start 2025-12-03 17:17:49 +01:00
Tour
febd08821a start 2025-12-03 15:40:19 +01:00
Tour
d3dc37576d start 2025-12-03 15:32:41 +01:00
Tour
aef7a3aa30 start 2025-12-03 15:32:34 +01:00
Tour
815d6a9a4a start 2025-12-03 15:18:21 +01:00
Tour
853c3cf53e start 2025-12-03 15:09:39 +01:00
7fa3e4a545 start 2025-11-28 14:43:58 +01:00
836ce3527f start 2025-11-28 06:37:04 +01:00
b174f77f6c start 2025-11-28 06:23:30 +01:00
0f5800441a start 2025-11-28 05:54:30 +01:00
f5ee240283 start 2025-11-28 05:46:53 +01:00
bde45e0dc9 start 2025-11-28 05:39:23 +01:00
5ab7d4f90d start 2025-11-28 05:28:20 +01:00
b560240c17 start 2025-11-28 05:16:51 +01:00
ec2efd4661 start 2025-11-28 05:05:33 +01:00
c26264b92a Allow shell commands in dokku run 2025-11-28 03:02:18 +01:00
9ff96cdf2f start 2025-11-28 02:22:28 +01:00
58b07e9e84 all 2025-11-28 02:22:17 +01:00
90ce6bc907 all 2025-11-28 02:19:48 +01:00
b1295f4329 all 2025-11-28 02:16:49 +01:00
026eb05912 all 2025-11-28 02:15:38 +01:00
0a2ea083df all 2025-11-28 02:12:53 +01:00
a11aa41cb2 start 2025-11-28 02:08:56 +01:00
7fbc9a2c96 all 2025-11-27 13:51:59 +01:00
d570da4b0b all 2025-11-27 13:44:14 +01:00
cd156de795 all 2025-11-27 13:42:18 +01:00
b57d395fed all 2025-11-27 13:39:29 +01:00
05d4a4bc63 all 2025-11-27 13:35:58 +01:00
858ed6a0cf all 2025-11-27 13:32:11 +01:00
20cb782a63 all 2025-11-27 13:28:09 +01:00
94061891e3 all 2025-11-27 13:20:40 +01:00
075fda64f6 all 2025-11-27 13:14:45 +01:00
c540518723 all 2025-11-27 13:11:01 +01:00
fad11d56b8 all 2025-11-27 13:08:30 +01:00
7ce6abd3e7 all 2025-11-27 12:54:54 +01:00
97b0fb09f0 all 2025-11-27 12:53:36 +01:00
15a4936310 all 2025-11-27 12:51:07 +01:00
913e0dfcc1 all 2025-11-27 12:45:01 +01:00
adb66611c1 all 2025-11-27 12:43:27 +01:00
b0304034cb all 2025-11-27 12:41:36 +01:00
6c42429214 all 2025-11-27 12:33:40 +01:00
4f1957fe95 start 2025-11-27 12:31:54 +01:00
7cb9599eda start 2025-11-27 12:24:59 +01:00
48fc49db9c start 2025-11-27 12:21:29 +01:00
1aa76771c4 start 2025-11-27 12:21:21 +01:00
afa52cb11c start 2025-11-27 08:15:26 +01:00
47854d8b39 start 2025-11-26 13:05:04 +01:00
9 changed files with 336 additions and 42 deletions

View File

@@ -0,0 +1,4 @@
---
apply: always
---

12
.aiignore Normal file
View File

@@ -0,0 +1,12 @@
# An .aiignore file follows the same syntax as a .gitignore file.
# .gitignore documentation: https://git-scm.com/docs/gitignore
# you can ignore files
.DS_Store
*.log
*.tmp
# or folders
dist/
build/
out/

1
.env Normal file
View File

@@ -0,0 +1 @@
GMAIL_APP_PASSWORD=agrepolhlnvhipkv

226
docs/EMAIL_CONFIGURATION.md Normal file
View File

@@ -0,0 +1,226 @@
# Email Notification Configuration Guide
## Overview
The application uses Gmail SMTP to send email notifications for auction alerts and lot updates.
## Gmail App Password Setup (Required for michael@appmodel.nl)
### Why App Passwords?
Google requires **App Passwords** instead of your regular Gmail password when using SMTP with 2-factor authentication enabled.
### Steps to Generate Gmail App Password:
1. **Enable 2-Factor Authentication** (if not already enabled)
- Go to https://myaccount.google.com/security
- Under "Signing in to Google", enable "2-Step Verification"
2. **Generate App Password**
- Go to https://myaccount.google.com/apppasswords
- Or navigate: Google Account → Security → 2-Step Verification → App passwords
- Select app: "Mail"
- Select device: "Other (Custom name)" → Enter "Auctiora Monitor"
- Click "Generate"
- Google will display a 16-character password (e.g., `abcd efgh ijkl mnop`)
- **Copy this password immediately** (you won't see it again)
3. **Use the App Password**
- Use this 16-character password (without spaces) in your configuration
- Format: `abcdefghijklmnop`
## Configuration
### Method 1: Environment Variable (Recommended for Production)
Set the `auction.notification.config` property in your `application.properties` or via environment variable:
```properties
# Format: smtp:username:password:recipient_email
auction.notification.config=smtp:michael@appmodel.nl:YOUR_APP_PASSWORD:michael@appmodel.nl
```
**Example with Docker:**
```bash
docker run -e AUCTION_NOTIFICATION_CONFIG="smtp:michael@appmodel.nl:abcdefghijklmnop:michael@appmodel.nl" ...
```
### Method 2: application.properties (Development)
Edit `src/main/resources/application.properties`:
```properties
# BEFORE (desktop only):
auction.notification.config=desktop
# AFTER (desktop + email):
auction.notification.config=smtp:michael@appmodel.nl:YOUR_APP_PASSWORD_HERE:michael@appmodel.nl
```
### Format Breakdown
The configuration string format is:
```
smtp:<SMTP_USERNAME>:<APP_PASSWORD>:<RECIPIENT_EMAIL>
```
Where:
- `SMTP_USERNAME`: Your Gmail address (michael@appmodel.nl)
- `APP_PASSWORD`: The 16-character app password from Google (no spaces)
- `RECIPIENT_EMAIL`: Email address to receive notifications (can be same as sender)
## Configuration Examples
### Desktop Notifications Only
```properties
auction.notification.config=desktop
```
### Email Notifications Only
```properties
auction.notification.config=smtp:michael@appmodel.nl:abcdefghijklmnop:michael@appmodel.nl
```
### Both Desktop and Email (Recommended)
The SMTP configuration automatically enables both:
```properties
auction.notification.config=smtp:michael@appmodel.nl:abcdefghijklmnop:michael@appmodel.nl
```
### Send to Multiple Recipients
To send to multiple recipients, you can modify the code or set up Gmail forwarding rules.
## SMTP Configuration Details
The application uses these Gmail SMTP settings (hardcoded):
- **Host**: smtp.gmail.com
- **Port**: 587
- **Security**: STARTTLS
- **Authentication**: Required
## Testing Configuration
After configuration, restart the application and check logs:
**Success:**
```
✓ OpenCV loaded successfully
Email notification: Test Alert
```
**Failure (wrong password):**
```
WARN NotificationService - Email failed: 535-5.7.8 Username and Password not accepted
```
## Troubleshooting
### Error: "Username and Password not accepted"
- **Cause**: Invalid App Password or 2FA not enabled
- **Solution**:
1. Verify 2-Factor Authentication is enabled
2. Generate a new App Password
3. Ensure no spaces in the password
4. Check for typos in email address
### Error: "AuthenticationFailedException"
- **Cause**: Incorrect credentials format
- **Solution**: Verify the format: `smtp:user:pass:recipient`
### Gmail Blocks Sign-in
- **Cause**: "Less secure app access" is disabled (deprecated by Google)
- **Solution**: Use App Passwords (as described above)
### Configuration Not Taking Effect
- **Cause**: Application not restarted or environment variable not set
- **Solution**:
1. Restart the application/container
2. Verify with: `docker logs auctiora | grep notification`
### SMTP Connection Timeout
- **Error**: `Couldn't connect to host, port: smtp.gmail.com, 587; timeout -1`
- **Causes**:
1. **Firewall/Network blocking port 587**
2. **Corporate network blocking SMTP**
3. **Antivirus/security software blocking connections**
4. **No internet access in test/container environment**
- **Solutions**:
1. **Test connectivity**:
```bash
# On Linux/Mac
telnet smtp.gmail.com 587
# On Windows
Test-NetConnection -ComputerName smtp.gmail.com -Port 587
```
2. **Check firewall rules**: Allow outbound connections to port 587
3. **Docker network**: Ensure container has internet access
```bash
docker exec auctiora ping -c 3 smtp.gmail.com
```
4. **Try alternative port 465** (SSL/TLS):
- Requires code change to use `mail.smtp.socketFactory`
5. **Corporate networks**: May require VPN or proxy configuration
6. **Windows Firewall**: Add Java/application to allowed programs
### Connection Succeeds but Authentication Fails
- **Error**: `Email authentication failed - check Gmail App Password`
- **Solution**: Verify App Password is correct and has no spaces
## Security Best Practices
1. **Never commit passwords to git**
- Use environment variables in production
- Add `application-local.properties` to `.gitignore`
2. **Rotate App Passwords periodically**
- Generate new App Password every 90 days
- Revoke old passwords at https://myaccount.google.com/apppasswords
3. **Use separate App Passwords per application**
- Creates "Auctiora Monitor" specific password
- Easy to revoke if compromised
4. **Monitor Gmail Activity**
- Check https://myaccount.google.com/notifications
- Review "Recent security activity"
## Example Docker Compose Configuration
```yaml
services:
auctiora:
image: auctiora:latest
environment:
- AUCTION_NOTIFICATION_CONFIG=smtp:michael@appmodel.nl:${GMAIL_APP_PASSWORD}:michael@appmodel.nl
- AUCTION_DATABASE_PATH=/mnt/okcomputer/output/cache.db
volumes:
- shared-auction-data:/mnt/okcomputer/output
```
Then set the password in `.env` file (not committed):
```bash
GMAIL_APP_PASSWORD=abcdefghijklmnop
```
## Notification Types
The application sends these email notifications:
1. **Lot Closing Soon** (Priority: High)
- Sent when a lot closes within 5 minutes
- Subject: `[Troostwijk] Lot nearing closure`
2. **Bid Updated** (Priority: Normal)
- Sent when current bid increases
- Subject: `[Troostwijk] Bid update`
3. **Critical Alerts** (Priority: High)
- System errors or important events
- Subject: `[Troostwijk] Critical Alert`
## Alternative: Desktop Notifications Only
If you don't want email notifications, use:
```properties
auction.notification.config=desktop
```
This will only show system tray notifications (Linux/Windows/Mac).

33
scripts/BFG.ps1 Normal file
View File

@@ -0,0 +1,33 @@
# BFG.ps1 (run from C:\vibe\auctiora\scripts)
$ErrorActionPreference = "Stop"
# 1) Download BFG jar once, next to this script
$bfgJar = Join-Path $PSScriptRoot "bfg.jar"
if (-not (Test-Path $bfgJar)) {
Invoke-WebRequest `
"https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar" `
-OutFile $bfgJar
}
# 2) Clone bare mirror next to project root: C:\vibe\auctiora\auctiora.git
$rootDir = Join-Path $PSScriptRoot ".."
$mirrorPath = Join-Path $rootDir "auctiora.git"
if (Test-Path $mirrorPath) {
Remove-Item $mirrorPath -Recurse -Force
}
git clone --mirror "https://git.appmodel.nl/Tour/auctiora.git" $mirrorPath
# 3) Run BFG in mirror
Push-Location $mirrorPath
java -jar $bfgJar --strip-blobs-bigger-than 50M .
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# 4) Force-push cleaned history
git push --force
Pop-Location

View File

@@ -26,7 +26,7 @@ public record ImageProcessingService(DatabaseService db, ObjectDetectionService
return true;
} catch (Exception e) {
log.error("Process fail {}: {}", id, e.getMessage());
log.warn("Process fail {}: {}", id, e.getMessage());
return false;
}
}
@@ -49,7 +49,7 @@ public record ImageProcessingService(DatabaseService db, ObjectDetectionService
log.info("Processed {}, detected {}", processed, detected);
} catch (Exception e) {
log.error("Batch fail: {}", e.getMessage());
log.warn("Batch fail: {}", e.getMessage());
}
}
}

View File

@@ -1,10 +1,17 @@
package auctiora;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import javax.mail.*;
import javax.mail.internet.*;
import java.awt.*;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.util.Date;
import java.util.Properties;
@@ -52,32 +59,43 @@ public record NotificationService(Config cfg) {
var props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.starttls.required", "true");
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.port", "587");
props.put("mail.smtp.ssl.trust", "smtp.gmail.com");
props.put("mail.smtp.ssl.protocols", "TLSv1.2");
// Connection timeouts (10 seconds each)
props.put("mail.smtp.connectiontimeout", "10000");
props.put("mail.smtp.timeout", "10000");
props.put("mail.smtp.writetimeout", "10000");
var session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(cfg.smtpUsername(), cfg.smtpPassword());
}
});
var m = new MimeMessage(session);
m.setFrom(new InternetAddress(cfg.smtpUsername()));
m.setRecipients(Message.RecipientType.TO, InternetAddress.parse(cfg.toEmail()));
m.setSubject("[Troostwijk] " + title);
m.setText(msg);
m.setSentDate(new Date());
if (prio > 0) {
m.setHeader("X-Priority", "1");
m.setHeader("Importance", "High");
}
Transport.send(m);
log.info("Email notification: {}", title);
log.info("Email notification sent: {}", title);
} catch (javax.mail.AuthenticationFailedException e) {
log.warn("Email authentication failed - check Gmail App Password: {}", e.getMessage());
} catch (javax.mail.MessagingException e) {
log.warn("Email connection failed (network/firewall issue?): {}", e.getMessage());
} catch (Exception e) {
log.warn("Email failed: {}", e.getMessage());
}

View File

@@ -56,12 +56,12 @@ public class AuctionRepository {
} catch (Exception e) {
// If UNIQUE constraint on url fails, try updating by url
String errMsg = e.getMessage();
var errMsg = e.getMessage();
if (errMsg != null && (errMsg.contains("UNIQUE constraint failed") ||
errMsg.contains("PRIMARY KEY constraint failed"))) {
log.debug("Auction conflict detected, attempting update by URL: {}", auction.url());
int updated = handle.createUpdate("""
var updated = handle.createUpdate("""
UPDATE auctions SET
auction_id = :auctionId,
title = :title,
@@ -102,7 +102,7 @@ public class AuctionRepository {
return jdbi.withHandle(handle ->
handle.createQuery("SELECT * FROM auctions")
.map((rs, ctx) -> {
String closingStr = rs.getString("closing_time");
var closingStr = rs.getString("closing_time");
LocalDateTime closingTime = null;
if (closingStr != null && !closingStr.isBlank()) {
try {
@@ -136,7 +136,7 @@ public class AuctionRepository {
handle.createQuery("SELECT * FROM auctions WHERE country = :country")
.bind("country", countryCode)
.map((rs, ctx) -> {
String closingStr = rs.getString("closing_time");
var closingStr = rs.getString("closing_time");
LocalDateTime closingTime = null;
if (closingStr != null && !closingStr.isBlank()) {
try {

View File

@@ -14,14 +14,14 @@ class NotificationServiceTest {
@Test
@DisplayName("Should initialize with desktop-only configuration")
void testDesktopOnlyConfiguration() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertNotNull(service);
}
@Test
@DisplayName("Should initialize with SMTP configuration")
void testSMTPConfiguration() {
NotificationService service = new NotificationService(
var service = new NotificationService(
"smtp:test@gmail.com:app_password:recipient@example.com"
);
assertNotNull(service);
@@ -52,7 +52,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send desktop notification without error")
void testDesktopNotification() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
// Should not throw exception even if system tray not available
assertDoesNotThrow(() ->
@@ -63,7 +63,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send high priority notification")
void testHighPriorityNotification() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertDoesNotThrow(() ->
service.sendNotification("Urgent message", "High Priority", 1)
@@ -73,7 +73,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send normal priority notification")
void testNormalPriorityNotification() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertDoesNotThrow(() ->
service.sendNotification("Regular message", "Normal Priority", 0)
@@ -83,7 +83,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should handle notification when system tray not supported")
void testNoSystemTraySupport() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
// Should gracefully handle missing system tray
assertDoesNotThrow(() ->
@@ -96,7 +96,7 @@ class NotificationServiceTest {
void testEmailNotificationWithValidConfig() {
// Note: This won't actually send email without valid credentials
// But it should initialize properly
NotificationService service = new NotificationService(
var service = new NotificationService(
"smtp:test@gmail.com:fake_password:test@example.com"
);
@@ -112,7 +112,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should include both desktop and email when SMTP configured")
void testBothNotificationChannels() {
NotificationService service = new NotificationService(
var service = new NotificationService(
"smtp:user@gmail.com:password:recipient@example.com"
);
@@ -125,7 +125,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should handle empty message gracefully")
void testEmptyMessage() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertDoesNotThrow(() ->
service.sendNotification("", "", 0)
@@ -135,9 +135,9 @@ class NotificationServiceTest {
@Test
@DisplayName("Should handle very long message")
void testLongMessage() {
NotificationService service = new NotificationService("desktop");
String longMessage = "A".repeat(1000);
var service = new NotificationService("desktop");
var longMessage = "A".repeat(1000);
assertDoesNotThrow(() ->
service.sendNotification(longMessage, "Long Message Test", 0)
);
@@ -146,7 +146,7 @@ class NotificationServiceTest {
@Test
@DisplayName("Should handle special characters in message")
void testSpecialCharactersInMessage() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertDoesNotThrow(() ->
service.sendNotification(
@@ -184,10 +184,10 @@ class NotificationServiceTest {
@Test
@DisplayName("Should handle multiple rapid notifications")
void testRapidNotifications() {
NotificationService service = new NotificationService("desktop");
var service = new NotificationService("desktop");
assertDoesNotThrow(() -> {
for (int i = 0; i < 5; i++) {
for (var i = 0; i < 5; i++) {
service.sendNotification("Notification " + i, "Rapid Test", 0);
}
});
@@ -205,10 +205,10 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send bid change notification format")
void testBidChangeNotificationFormat() {
NotificationService service = new NotificationService("desktop");
String message = "Nieuw bod op kavel 12345: €150.00 (was €125.00)";
String title = "Kavel bieding update";
var service = new NotificationService("desktop");
var message = "Nieuw bod op kavel 12345: €150.00 (was €125.00)";
var title = "Kavel bieding update";
assertDoesNotThrow(() ->
service.sendNotification(message, title, 0)
@@ -218,10 +218,10 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send closing alert notification format")
void testClosingAlertNotificationFormat() {
NotificationService service = new NotificationService("desktop");
String message = "Kavel 12345 sluit binnen 5 min.";
String title = "Lot nearing closure";
var service = new NotificationService("desktop");
var message = "Kavel 12345 sluit binnen 5 min.";
var title = "Lot nearing closure";
assertDoesNotThrow(() ->
service.sendNotification(message, title, 1)
@@ -231,10 +231,10 @@ class NotificationServiceTest {
@Test
@DisplayName("Should send object detection notification format")
void testObjectDetectionNotificationFormat() {
NotificationService service = new NotificationService("desktop");
String message = "Lot contains: car, truck, machinery\nEstimated value: €5000";
String title = "Object Detected";
var service = new NotificationService("desktop");
var message = "Lot contains: car, truck, machinery\nEstimated value: €5000";
var title = "Object Detected";
assertDoesNotThrow(() ->
service.sendNotification(message, title, 0)