The QSO entries on the award detail page were missing the band field,
causing the openQSODetailModal() function to fail with "QSO not found"
when trying to fetch full QSO details. The band field is now included
in the QSO entry objects so the filter parameters match correctly.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Click on any QSO row to view detailed information in modal
- Display all QSO fields organized by section:
- Basic information (callsign, date, time, band, mode)
- Location (entity, DXCC, grid, continent, zones, state, county)
- Satellite info (when applicable)
- DOK information (partner's and my DOK)
- Confirmation status (LoTW and DCL with dates and status)
- Keyboard accessible (Enter to open, Escape to close)
- Close by clicking backdrop, pressing Escape, or × button
- Hover effect on rows to indicate clickability
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add category filter dropdown to awards list page
- Filters awards by category (dxcc, darc, was, etc.)
- Remove unused "Sort by" dropdown from award detail page
- Categories auto-extracted from award definitions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical bug fix: ADIF parser was using case-sensitive split on '<EOR>',
but LoTW returns lowercase '<eor>' tags. This caused all 242,239 QSOs
to be parsed as a single giant record with fields overwriting each other,
resulting in only 1 QSO being imported.
Changes:
- Changed EOR split from case-sensitive to case-insensitive regex
- Removes all debug logging
- Restored normal incremental/first-sync LoTW logic
Before: 6.8MB LoTW report → 1 QSO (bug)
After: 6.8MB LoTW report → All 242K+ QSOs (fixed)
Also includes:
- Previous fix: Added missing timeOn to LoTW duplicate detection
- Previous fix: Replaced regex.exec() while loop with matchAll() for-of
Tested with limited date range (2025-10-01) and confirmed 420 QSOs
imported successfully.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical bug: ADIF parser was only parsing 1 QSO from multi-MB LoTW reports.
Root cause: The regex.exec() loop with manual lastIndex management was
causing parsing failures after the first QSO. The while loop approach with
regex state management was error-prone.
Fix: Replaced regex.exec() while loop with matchAll() for-of iteration.
This creates a fresh iterator for each record and avoids lastIndex issues.
Before: 6.8MB LoTW report → 1 QSO parsed
After: 6.8MB LoTW report → All QSOs parsed
The matchAll() approach is cleaner and more reliable for parsing ADIF
records with multiple fields.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Critical bug: LoTW sync was missing timeOn in the duplicate detection
query, causing multiple QSOs with the same callsign/date/band/mode
but different times to be treated as duplicates.
Example: If you worked DL1ABC on 2025-01-15 at 10:00, 12:00, and 14:00
all on 80m CW, only the first QSO would be imported.
Now matches DCL sync logic which correctly includes timeOn:
- userId, callsign, qsoDate, timeOn, band, mode
This ensures all unique QSOs are properly imported from LoTW.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The DOK award type (used for DLD award) now supports filtering by band,
mode, and other QSO fields. This allows creating award variants like:
- DLD on specific bands (80m, 40m, etc.)
- DLD on specific modes (CW, SSB, etc.)
- DLD with combined filters (e.g., 80m + CW)
Changes:
- Modified calculateDOKAwardProgress() to apply filters before processing
- Added example awards: dld-80m, dld-40m, dld-cw, dld-80m-cw
- Filter system uses existing applyFilters() function
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When accessing client-side routes (like /qsos) via curl or non-JS clients,
the server attempted to open them as static files, causing Bun to throw
an unhandled ENOENT error that showed an ugly error page.
Now checks if a path has a file extension before attempting to serve it.
Paths without extensions are immediately served index.html for SPA routing.
Also improves the 503 error page with user-friendly HTML when frontend build
is missing.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix logger bug where debug level (0) was treated as falsy
- Change `||` to `??` in config.js to properly handle log level 0
- Debug logs now work correctly when LOG_LEVEL=debug
- Add server startup logging
- Log port, environment, and log level on server start
- Helps verify configuration is loaded correctly
- Add DCL API request debug logging
- Log full API request parameters when LOG_LEVEL=debug
- API key is redacted (shows first/last 4 chars only)
- Helps troubleshoot DCL sync issues
- Update CLAUDE.md documentation
- Add Logging section with log levels and configuration
- Document debug logging feature for DCL service
- Add this fix to Recent Commits section
Note: .env file added locally with LOG_LEVEL=debug (not committed)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When DCL sync updates a QSO without DOK fields, the previous code would
write empty strings to the database, overwriting any existing DOK data
that was previously imported or manually entered.
Now only updates DOK/grid fields when DCL actually provides non-empty
values, preserving existing data from other sources.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When syncing from LoTW/DCL, only update QSOs if confirmation data
has changed. This avoids unnecessary database updates and makes it
clear which QSOs actually changed.
- LoTW: Checks if lotwQslRstatus or lotwQslRdate changed
- DCL: Checks if dclQslRstatus, dclQslRdate, DOK, or grid changed
- Frontend: Shows skipped count in sync summary
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add import log display that shows QSOs imported via LoTW/DCL sync.
Backend now tracks added/updated QSOs (callsign, date, band, mode)
and returns them in sync result. Frontend displays tables showing
new and updated QSOs after sync completes.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add shared ADIF parser utility (src/backend/utils/adif-parser.js)
- parseADIF(): Parse ADIF format into QSO records
- parseDCLResponse(): Parse DCL's JSON response format
- normalizeBand() and normalizeMode(): Standardize band/mode names
- Implement DCL service (src/backend/services/dcl.service.js)
- fetchQSOsFromDCL(): Fetch from DCL API (ready for API availability)
- parseDCLJSONResponse(): Parse example payload format
- syncQSOs(): Update existing QSOs with DCL confirmations
- Support DCL-specific fields: DCL_QSL_RCVD, DCL_QSLRDATE, DARC_DOK, MY_DARC_DOK
- Refactor LoTW service to use shared ADIF parser
- Remove duplicate parseADIF, normalizeBand, normalizeMode functions
- Import from shared utility for consistency
- Tested with example DCL payload
- Successfully parses all 6 QSOs
- Correctly extracts DCL confirmation data
- Handles ADIF format with <EOR> delimiters
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add DOK-based award tracking with DCL confirmation. Counts unique
(DOK, band, mode) combinations toward the 100 DOK target.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add infrastructure for future DARC Community Logbook (DCL) integration:
- Database schema: Add dcl_api_key, my_darc_dok, darc_dok, dcl_qsl_rdate, dcl_qsl_rstatus fields
- Create DCL service stub with placeholder functions for when DCL provides API
- Backend API: Add /api/auth/dcl-credentials endpoint for API key management
- Frontend settings: Add DCL API key input with informational notice about API availability
- QSO table: Add My DOK and DOK columns, update confirmation column for multiple services
Note: DCL download API is not yet available. These changes prepare the application
for future implementation when DCL adds programmatic access.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update all page titles and branding from "Ham Radio Awards" to "Quickawards by DJ7NT"
- Simplify footer copyright text
- Redirect users to login page after logout instead of home page
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace Status column with Confirmed column that displays both the QSL service type (LoTW) and confirmation date, making it ready for future QSL services like eQSL and ClubLog.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Frontend now uses @sveltejs/adapter-static for production builds
- Backend serves both API and static files from single port (originally port 3000)
- Removed all throw statements from services to avoid Elysia prototype errors
- Fixed favicon serving and SvelteKit assets path handling
- Added ecosystem.config.js for PM2 process management
- Comprehensive deployment documentation (PM2 + HAProxy)
- Updated README with single-port architecture
- Created start.sh script for easy production start
- Fix WAS award filter to use entityId (291) instead of entity string
- Fix backend to use normalized rules.target for all award types
- Fix frontend to distinguish point-based from entity-based awards
- Fix "Needed" calculation for entity awards: target - worked
- Remove points display from non-point awards (WAS, DXCC, etc.)
- Add proper target field to all award API responses
Fixes issue where:
- WAS showed 0 states (filter didn't match "UNITED STATES OF AMERICA")
- DXCC CW showed "Needed: 0" (target not extracted from nested rules)
- Entity awards showed "Points: NaN" (incorrectly detected as point-based)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add caption field to all award definitions with detailed rule explanations
- Rename Special Stations Award to Wavelog Award
- Add configurable countMode for point-based awards:
- perBandMode: count unique (callsign, band, mode) combinations
- perStation: count each station once
- perQso: count every QSO
- Update backend to respect countMode in progress calculation
- Add target field to award API responses
- Fix "Needed" calculation for point-based awards
- Display caption on award detail pages with styled info box
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rename 'stations' variable to 'stationList' to avoid conflict with
destructured 'stations' from rules.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement a new award type where stations have different point values.
Each confirmed QSO with a special station adds its points to the total.
New Award: Special Stations Award
- 10-point stations: DF2ET, DJ7NT, HB9HIL, LA8AJA
- 5-point stations: DB4SCW, DG2RON, DG0TM, DO8MKR
- Target: 50 points to complete
Backend Changes
- Add support for 'points' award type
- Add calculatePointsAwardProgress() for point calculation
- Add getPointsAwardEntityBreakdown() for station details
- Track worked, confirmed, and totalPoints separately
Frontend Changes
- Awards page: Show stations and points for point awards
- Details page: Show point summary cards
- Details page: Show points badge next to station callsign
- Gold/amber gradient badge for point values
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
For VUCC awards, ensure the grid square is truncated to 4 characters
both when counting unique entities AND when displaying.
This fixes VUCC award details to show 'FN31' instead of 'FN31pr',
making it clear that we're counting unique 4-character grid squares.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add configurable displayField to award definitions to control what
is shown in award details instead of always using the entity value.
## Award Definitions Updated
- DXCC: displayField = 'entity' (shows country name)
- DXCC CW: displayField = 'entity' (shows country name)
- WAS: displayField = 'state' (shows state name)
- VUCC: displayField = 'grid' (shows grid square)
- RS-44: displayField = 'callsign' (shows callsign)
## Backend Changes
- Preserve displayField when normalizing award rules
- Use displayField to determine entity name in details
- Fallback logic for awards without displayField
- Add satName to entity data for better display
This fixes VUCC showing grid squares instead of country names.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add entityName field to backend entity breakdown
- For DXCC: Show country name (e.g., 'Germany') with ID in parentheses
- For WAS: Show state name
- For VUCC: Show grid square
- For RS-44: Show callsign
- Update sorting to use entity names for better UX
- Add subtle styling for entity IDs (gray, smaller font)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Handle 'counter' type awards that count unique callsigns
- Add debug logging to track QSO filtering progress
- Normalize counter awards to use entityType: 'callsign'
This fixes RS-44 satellite award to count unique callsigns worked
via RS-44 satellite.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Convert entity to string before calling localeCompare to handle
numeric entity IDs (like DXCC entityId values).
Fixes TypeError on award detail pages when sorting by name.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Frontend
- Create award detail page at /awards/[id]
- Show all entities with worked/confirmed status
- Add filtering (all, worked, confirmed, unworked)
- Add sorting (name, status)
- Display summary cards (total, confirmed, worked, needed)
- Show entity details (callsign, band, mode, date)
## Backend Fixes
- Fix award progress calculation for filtered awards
- Add normalizeAwardRules to handle "filtered" type awards
- Fix satellite filter to check satName field instead of satellite
- Add case-insensitive contains matching
- Apply normalization to both progress and entity breakdown functions
This fixes the 0/0 issue for DXCC CW, WAS, VUCC, and satellite awards.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement awards display with progress calculation based on QSO data.
## Backend
- Add awards service with progress calculation logic
- Support for DXCC, WAS, VUCC, and satellite awards
- Filter QSOs by band, mode, and other criteria
- Calculate worked/confirmed entities per award
- API endpoints:
- GET /api/awards - List all awards
- GET /api/awards/:id/progress - Get award progress
- GET /api/awards/:id/entities - Get detailed entity breakdown
## Frontend
- Create awards listing page with progress cards
- Add Awards link to navigation bar
- Display award progress bars with worked/confirmed counts
- Link to individual award detail pages (to be implemented)
- Copy award definitions to static folder
## Award Definitions
- DXCC Mixed Mode (100 entities)
- DXCC CW (100 entities, CW only)
- WAS Mixed Mode (50 states)
- VUCC Satellite (100 grids)
- RS-44 Satellite Award (100 QSOs)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix the suppressURIErrorPlugin to properly handle malformed URIs from
browser extensions without throwing errors.
Changes:
- Move next() outside try block to only call for valid URLs
- Return 200 OK instead of 400 for malformed URIs
- Early return to prevent further processing of bad requests
Fixes errors appearing on every page load from browser extension requests.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add environment variable configuration to support deployment on custom
domains like https://awards.dj7nt.de
## Changes
- Add .env.example with configuration template
- Update API client to use VITE_API_BASE_URL with fallback to /api
- Update SvelteKit config to use VITE_APP_URL for CSRF trusted origins
- Update backend CORS to use configurable allowed origins
- Add ALLOWED_ORIGINS environment variable for production
- Add build and preview scripts to package.json
- Update README with production deployment guide and nginx example
## Environment Variables
- VITE_APP_URL: Application hostname (e.g., https://awards.dj7nt.de)
- VITE_API_BASE_URL: API base URL (empty = relative paths)
- ALLOWED_ORIGINS: Comma-separated CORS origins
- JWT_SECRET: Strong secret for production
- NODE_ENV: development/production
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Configure SvelteKit dev server to proxy API requests to Elysia backend,
allowing users to access the application on a single port (5173).
## Changes
- Add proxy config to vite.config.js for /api requests
- Update API client to use relative URLs (/api)
- Add convenience 'dev' script to start both servers
- Update README with single-port instructions
- Add architecture section explaining proxy setup
Benefits:
- Single port to access (5173)
- HMR still works for frontend
- No CORS issues
- Backend runs hidden on port 3001
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Backend
- Add Pino logging framework with timestamps and structured output
- Replace all console.error statements (49+) with proper logging levels
- Fix Drizzle ORM bug: replace invalid .get() calls with .limit(1)
- Remove unused auth routes file (already in index.js)
- Make internal functions private (remove unnecessary exports)
- Simplify code by removing excessive debug logging
## Frontend
- Add navigation bar to layout with:
- User's callsign display
- Navigation links (Dashboard, QSOs, Settings)
- Logout button with red color distinction
- Navigation only shows when user is logged in
- Dark themed design matching footer
## Documentation
- Update README.md with new project structure
- Update docs/DOCUMENTATION.md with logging and nav bar info
- Add logger.js to configuration section
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add backend pagination support for /api/qsos endpoint (page, limit params)
- Return paginated results with metadata (totalCount, totalPages, hasNext, hasPrev)
- Add pagination UI to QSOs page with prev/next buttons and page numbers
- Add back button to QSO Log and Settings pages linking to main menu
- Reset to page 1 when applying filters
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend changes:
- Add sync_jobs table for background job tracking with Drizzle schema
- Create job queue service (job-queue.service.js) for async job processing
- Only ONE active sync job per user enforced at queue level
- Refactor LoTW service with Wavelog download logic:
- Validate for "Username/password incorrect" in response
- Check file starts with "ARRL Logbook of the World Status Report"
- Use last LoTW QSL date for incremental sync (qso_qslsince)
- Wavelog-compatible timeouts and error handling
- Add deleteQSOs function to clear all user QSOs
- Fix database path to use absolute path for consistency
- Register job processor for lotw_sync job type
API endpoints:
- POST /api/lotw/sync - Queue background sync job, returns jobId immediately
- GET /api/jobs/:jobId - Get job status with progress tracking
- GET /api/jobs/active - Get user's active job
- GET /api/jobs - Get user's recent jobs
- DELETE /api/qsos/all - Delete all QSOs for authenticated user
Frontend changes:
- Add job polling every 2 seconds during sync
- Show real-time progress indicator during sync
- Add "Clear All QSOs" button with type-to-confirm ("DELETE")
- Check for active job on mount to resume polling after refresh
- Clean up polling interval on component unmount
- Update API client with jobsAPI methods (getStatus, getActive, getRecent)
Database:
- Add sync_jobs table: id, userId, status, type, startedAt, completedAt,
result, error, createdAt
- Foreign key to users table
- Path fix: now uses src/backend/award.db consistently
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add detailed documentation covering architecture, components, code structure
- Document award system with multiple examples (DXCC, WAS, VUCC, Satellite)
- Implement LoTW sync service with ADIF parsing and long-polling
- Add QSO logbook page with filtering and statistics
- Add settings page for LoTW credentials management
- Add API endpoints for LoTW sync, QSO retrieval, and statistics
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>