Commit Graph

37 Commits

Author SHA1 Message Date
310b1547c4 revert: remove LoTW sync type support (QSL/QSO delta/full)
This reverts commit 5b78935 which added:
- Sync type parameter (qsl_delta, qsl_full, qso_delta, qso_full)
- getLastLoTWQSODate() function
- Sync type dropdown on QSO page
- Job queue handling of sync types

Reason: LoTW doesn't provide DXCC entity data for unconfirmed QSOs,
which causes award calculation issues. Going back to QSL-only sync.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 12:28:23 +01:00
688b0fc255 feat: add "Confirmed by at least 1 service" filter to QSO view
- Add 'any' confirmation type filter showing QSOs confirmed by LoTW OR DCL
- Backend logic: lotwQslRstatus = 'Y' OR dclQslRstatus = 'Y'
- Frontend dropdown option positioned after "All QSOs"
- Shows all QSOs confirmed by at least one service (LoTW, DCL, or both)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 12:14:52 +01:00
5b7893536e feat: add LoTW sync type support (QSL/QSO delta/full)
- Add syncType parameter to LoTW sync: 'qsl_delta', 'qsl_full', 'qso_delta', 'qso_full'
- qsl_* = only confirmed QSOs (qso_qsl=yes)
- qso_* = all QSOs confirmed+unconfirmed (qso_qsl=no)
- delta = incremental sync with date filter
- full = sync all records without date filter
- Add getLastLoTWQSODate() for QSO-based incremental sync
- Add sync type dropdown selector on QSO page
- Update job queue service to handle sync types
- Update API endpoint to accept syncType in request body

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 12:09:28 +01:00
a50b4ae724 feat: add sync job cancel and rollback with real-time updates
Implement comprehensive sync job management with rollback capabilities
and real-time status updates on the dashboard.

## Features

### Cancel & Rollback
- Users can cancel failed or stale (>1h) sync jobs
- Rollback deletes added QSOs and restores updated QSOs to previous state
- Uses qso_changes table to track all modifications with before/after snapshots
- Server-side validation prevents cancelling completed or active jobs

### Database Changes
- Add qso_changes table to track QSO modifications per job
- Stores change type (added/updated), before/after data snapshots
- Enables precise rollback of sync operations
- Migration script included

### Real-time Updates
- Dashboard now polls for job updates every 2 seconds
- Smart polling: starts when jobs active, stops when complete
- Job status badges update in real-time (pending → running → completed)
- Cancel button appears/disappears based on job state

### Backend
- Fixed job ordering to show newest first (desc createdAt)
- Track all QSO changes during LoTW/DCL sync operations
- cancelJob() function handles rollback logic
- DELETE /api/jobs/:jobId endpoint for cancelling jobs

### Frontend
- jobsAPI.cancel() method for cancelling jobs
- Dashboard shows last 5 sync jobs with status, stats, duration
- Real-time job status updates via polling
- Cancel button with confirmation dialog
- Loading state and error handling

### Logging Fix
- Changed from Bun.write() to fs.appendFile() for reliable log appending
- Logs now persist across server restarts instead of being truncated

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 11:46:19 +01:00
f50ec5f44e perf: implement Phase 1 backend performance optimizations
Fix N+1 query, add database indexes, and implement award progress caching:

- Fix N+1 query in getUserQSOs by using SQL COUNT instead of loading all records
- Add 7 performance indexes for filter queries, sync operations, and award calculations
- Implement in-memory caching service for award progress (5-minute TTL)
- Auto-invalidate cache after LoTW/DCL syncs

Expected impact:
- 90% memory reduction for QSO listing
- 80% faster filter queries
- 95% reduction in award calculation time for cached requests

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 14:18:00 +01:00
bee02d16ce fix: count QSOs confirmed by either LoTW or DCL in stats
QSO stats were only counting LoTW-confirmed QSOs, excluding
QSOs confirmed only by DCL from the "confirmed" count.

Changed getQSOStats() to count QSOs as confirmed if EITHER
LoTW OR DCL has confirmed them:
- Before: q.lotwQslRstatus === 'Y'
- After: q.lotwQslRstatus === 'Y' || q.dclQslRstatus === 'Y'

Fixes stats showing 8317/8338 confirmed when all QSOs were
confirmed by at least one system.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 13:32:45 +01:00
b40d3639f7 feat: add callsign to DLD award entity details
The DLD award detail page was only showing the mode in QSO entries
because the entity breakdown didn't include the callsign field.

Changes:
- Backend: Add callsign field to DOK award entity details

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 13:12:25 +01:00
9dc8c8b678 fix: use qsoId for fetching QSO details in award page modal
The award page was filtering QSOs by callsign/date/band/mode, which
could return the wrong QSO when multiple QSOs with the same callsign
exist on the same band/mode combination.

Changes:
- Backend: Add qsoId field to award entity breakdown responses
- Backend: Add GET /api/qsos/:id endpoint to fetch QSO by ID
- Backend: Implement getQSOById() function in lotw.service.js
- Frontend: Update openQSODetailModal() to fetch by qsoId instead of filtering
- Frontend: Include qsoId in QSO entry objects for modal click handler

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 13:08:19 +01:00
8d47e6e4ad feat: add QSO page filters and fix DXCC entity priority
- Add confirmation type filter (LoTW Only, DCL Only, Both, None)
- Add search box for callsign, entity, and grid square
- Rename "All Confirmation" to "All QSOs" for clarity
- Fix exclusive filter logic using separate SQL conditions
- Add debug logging for filter troubleshooting
- Implement DXCC priority: LoTW > DCL
- Document QSO page filters and DXCC handling in CLAUDE.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 08:01:59 +01:00
b422c20463 Filters 2026-01-19 07:39:33 +01:00
645f7863e7 fix: add missing timeOn field to LoTW duplicate detection
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>
2026-01-18 07:49:39 +01:00
7f77c3adc9 feat: add filter support for DOK awards
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>
2026-01-18 07:30:15 +01:00
223461f536 fix: enable debug logging and improve DCL sync observability
- 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>
2026-01-18 07:02:52 +01:00
27d2ef14ef fix: preserve DOK data when DCL doesn't send values
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>
2026-01-18 06:35:04 +01:00
e09ab94e63 feat: skip QSOs with unchanged confirmation data
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>
2026-01-17 19:49:56 +01:00
3592dbb4fb feat: add import log showing synced QSOs
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>
2026-01-17 19:39:15 +01:00
8a1a5804ff feat: implement DCL ADIF parser and service integration
- 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>
2026-01-17 11:49:36 +01:00
c982dcd0fe feat: implement DLD (Deutschland Diplom) award
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>
2026-01-17 11:22:01 +01:00
47738c68a9 feat: prepare database and UI for DCL integration
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>
2026-01-17 10:24:43 +01:00
0db6b68f48 refactor: simplify codebase and replace external dependencies with Bun built-ins
Backend changes:
- Merge duplicate award logic (calculatePointsAwardProgress + getPointsAwardEntityBreakdown)
- Simplify LoTW service (merge syncQSOs functions, simplify polling)
- Remove job queue abstraction (hardcode LoTW sync, remove processor registry)
- Consolidate config files (database.js, logger.js, jwt.js → single config.js)
- Replace bcrypt with Bun.password.hash/verify
- Replace Pino logger with console-based logger
- Fix: export syncQSOs and getLastLoTWQSLDate for job queue imports
- Fix: correct database path resolution using new URL()

Frontend changes:
- Simplify auth store (remove localStorage wrappers, reduce from 222→109 lines)
- Consolidate API layer (remove verbose JSDoc, 180→80 lines)
- Add shared UI components (Loading, ErrorDisplay, BackButton)

Dependencies:
- Remove bcrypt (replaced with Bun.password)
- Remove pino and pino-pretty (replaced with console logger)

Total: ~445 lines removed (net), 3 dependencies removed
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 18:27:10 +01:00
907dc48f1b feat: Single-port deployment with improved error handling and SvelteKit static build
- 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
2026-01-16 13:58:03 +01:00
9b671795d7 Fix WAS award and improve award progress display
- 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>
2026-01-16 10:41:27 +01:00
436e9c2278 Add award rule captions and configurable point counting modes
- 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>
2026-01-16 10:14:41 +01:00
ac79645477 Fix variable name conflict in getPointsAwardEntityBreakdown
Rename 'stations' variable to 'stationList' to avoid conflict with
destructured 'stations' from rules.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 09:42:10 +01:00
a1886a94c8 Add point-based award system with station-specific values
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>
2026-01-16 09:33:53 +01:00
f9283cd632 Truncate grid display to 4 characters for VUCC awards
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>
2026-01-16 09:16:24 +01:00
052f0d13bd Add displayField configuration to award definitions
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>
2026-01-16 09:12:07 +01:00
3b7dfd74fe Display entity names instead of IDs in award details
- 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>
2026-01-16 09:05:50 +01:00
b3567100c0 Add counter award type support and debug logging
- 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>
2026-01-16 09:00:59 +01:00
d77ee69daa Add award detail page and fix award progress calculation
## 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>
2026-01-16 08:53:23 +01:00
884bdb436d Add awards system with progress tracking
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>
2026-01-16 08:47:30 +01:00
d959235cdd Add structured logging, navigation bar, and code cleanup
## 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>
2026-01-16 08:11:57 +01:00
40a3e3642e Add pagination to QSO Log and back buttons to pages
- 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>
2026-01-15 22:10:23 +01:00
dde0c18f51 1st sync Full 2026-01-15 22:05:09 +01:00
f82fc876ce Refactor LoTW sync with background job queue and Wavelog compatibility
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>
2026-01-15 21:47:50 +01:00
44c13e1bdc Add comprehensive documentation and LoTW integration
- 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>
2026-01-15 16:39:49 +01:00
8c26fc93e3 Initial commit: Ham Radio Award Portal
Features implemented:
- User authentication (register/login) with JWT
- SQLite database with Drizzle ORM
- SvelteKit frontend with authentication flow
- ElysiaJS backend with CORS enabled
- Award definition JSON schemas (DXCC, WAS, VUCC, SAT)
- Responsive dashboard with user profile

Tech stack:
- Backend: ElysiaJS, Drizzle ORM, SQLite, JWT
- Frontend: SvelteKit, Svelte stores
- Runtime: Bun
- Language: JavaScript

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-15 11:01:10 +01:00