Document the modeGroups property for award definitions, which allows
creating convenient multi-mode filters in the award detail view.
Co-Authored-By: Claude <noreply@anthropic.com>
Add documentation for the new configurable mode groups feature:
- CLAUDE.md: Add modeGroups to Award Rule Options section
- CLAUDE.md: Update Award Detail View section with mode group info
- CLAUDE.md: Add to Recent Development Work (January 2026)
- README.md: Add GET /api/awards/:awardId endpoint
- README.md: Add new Mode Groups section in Features in Detail
Co-Authored-By: Claude <noreply@anthropic.com>
Add per-award configurable mode groups for filtering multiple modes
together in the award detail view. Mode groups are displayed with
visual separators in the mode filter dropdown.
Backend changes:
- Add modeGroups to getAllAwards() return mapping
- Add getAwardById() function to fetch single award definition
- Add GET /api/awards/:awardId endpoint
Frontend changes:
- Fetch award definition separately to get modeGroups
- Update availableModes to include mode groups with separator
- Update filteredEntities logic to handle mode groups
- Update groupDataForTable() and applyFilter() for mode groups
- Disable separator option in dropdown
Award definitions:
- DXCC: Add Digi-Modes, Classic Digi-Modes, Mixed-Mode w/o WSJT-Modes,
Phone-Modes groups
- DLD: Add same mode groups (adjusted for available modes)
Co-Authored-By: Claude <noreply@anthropic.com>
When auto-sync is enabled but credentials (LoTW/DCL) are removed, the
scheduler would continuously try to sync every minute, logging the same
warning forever.
Now:
- Split pending users into those with and without credentials
- For users without credentials, update nextSyncAt to retry in 24 hours
- Log a warning with affected user IDs
- Only return users with valid credentials for job processing
This prevents log spam and unnecessary database queries while still
periodically checking if credentials have been restored.
Co-Authored-By: Claude <noreply@anthropic.com>
Add automatic synchronization scheduler that allows users to configure
periodic sync intervals for LoTW and DCL via the settings page.
Features:
- Users can enable/disable auto-sync per service (LoTW/DCL)
- Configurable sync intervals (1-720 hours)
- Settings page UI for managing auto-sync preferences
- Dashboard shows upcoming scheduled auto-sync jobs
- Scheduler runs every minute, triggers syncs when due
- Survives server restarts via database persistence
- Graceful shutdown support (SIGINT/SIGTERM)
Backend:
- New autoSyncSettings table with user preferences
- auto-sync.service.js for CRUD operations and scheduling logic
- scheduler.service.js for periodic tick processing
- API endpoints: GET/PUT /auto-sync/settings, GET /auto-sync/scheduler/status
Frontend:
- Auto-sync settings section in settings page
- Upcoming auto-sync section on dashboard with scheduled job cards
- Purple-themed UI for scheduled jobs with countdown animation
Co-Authored-By: Claude <noreply@anthropic.com>
- Delete duplicate getCacheStats() function in cache.service.js
- Fix date calculation bug in lotw.service.js (was Date.now()-Date.now())
- Extract duplicate helper functions (yieldToEventLoop, getQSOKey) to sync-helpers.js
- Cache award definitions in memory to avoid repeated file I/O
- Delete unused parseDCLJSONResponse() function
- Remove unused imports (getPerformanceSummary, resetPerformanceMetrics)
- Auto-discover award JSON files instead of hardcoded list
Co-Authored-By: Claude <noreply@anthropic.com>
Rewrote documentation to reflect current state of the application:
- Updated award list (DXCC, DXCC SAT, DLD, WAS, VUCC SAT, SAT-RS44, 73 on 73)
- Added allowed_bands and satellite_only rule options
- Added DXCC SAT award documentation
- Added QSO deletion endpoint
- Updated award detail view features (unique entity progress, satellite grouping, band sorting)
- Removed outdated DLD variant examples
- Streamlined and reorganized sections
Co-Authored-By: Claude <noreply@anthropic.com>
After deleting all QSOs, invalidate the stats and user caches so the
QSO page shows updated statistics instead of stale cached data.
Co-Authored-By: Claude <noreply@anthropic.com>
The qso_changes table has a foreign key reference to qsos.id, which
was preventing QSO deletion. Now deletes related qso_changes records
first before deleting QSOs.
Also added better error logging to the DELETE endpoint.
Co-Authored-By: Claude <noreply@anthropic.com>
The db.delete() returns a result object with a 'changes' property
indicating the number of affected rows, not the count directly.
Co-Authored-By: Claude <noreply@anthropic.com>
Added new award "DXCC SAT" that only counts satellite QSOs (QSOs with
satName field set). This adds a new "satellite_only" key to award
definitions that filters to only include satellite communications.
Award definition:
- ID: dxcc-sat
- Name: DXCC SAT
- Target: 100 DXCC entities
- Only satellite QSOs count
Co-Authored-By: Claude <noreply@anthropic.com>
Removed dld-80m-cw.json award definition. Only the main DLD award
remains, which covers all bands and modes.
Co-Authored-By: Claude <noreply@anthropic.com>
Removed the following DLD award variants:
- dld-80m.json
- dld-40m.json
- dld-cw.json
Kept dld-80m-cw.json as it represents a more specific combination.
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a new "allowed_bands" key to award definitions that restricts which
bands count toward an award. If absent, all bands are allowed (default
behavior).
Applied to DXCC award to only count HF bands (160m-10m), excluding
VHF/UHF bands like 6m, 2m, and 70cm.
Co-Authored-By: Claude <noreply@anthropic.com>
- Removed dxcc-cw.json award definition
- Renamed "DXCC Mixed Mode" to "DXCC" in dxcc.json
- Changed award ID from "dxcc-mixed" to "dxcc"
- Removed dxcc-cw.json from awards service file list
Co-Authored-By: Claude <noreply@anthropic.com>
Band columns are now sorted by wavelength (longest to shortest):
160m, 80m, 60m, 40m, 30m, 20m, 17m, 15m, 12m, 10m, 6m, 2m, 70cm, SAT.
Unknown bands are sorted to the end.
Co-Authored-By: Claude <noreply@anthropic.com>
Column sums now correctly count unique entities (e.g., unique DXCC
countries per band) instead of counting individual entity entries or
QSOs. This matches the award progress semantics.
Co-Authored-By: Claude <noreply@anthropic.com>
The SAT column sum was always showing 0 because it was filtering by
e.band === 'SAT', but entities still have their original band in the
data. Now it correctly identifies satellite QSOs by checking if any
QSOs have satName.
Co-Authored-By: Claude <noreply@anthropic.com>
Satellite QSOs are now grouped under a "SAT" column instead of their
frequency band. The backend now includes satName in QSO data, and the
frontend detects satellite QSOs and groups them appropriately.
Co-Authored-By: Claude <noreply@anthropic.com>
Summary cards now display unique entity counts (e.g., unique DXCC countries)
instead of per-band/mode slot counts. This shows actual award progress:
Total entities worked, confirmed, and needed to reach the award target.
Co-Authored-By: Claude <noreply@anthropic.com>
Summary cards (Total, Confirmed, Worked, Needed) now update based on the
selected mode filter. Also removed redundant mode display from table column
headers since the mode is already visible in the filter dropdown.
Co-Authored-By: Claude <noreply@anthropic.com>
- Award detail page now shows QSO counts per (entity, band, mode) slot
- Click count to open modal with all QSOs for that slot
- Click QSO in list to view full details
- Add mode filter: "Mixed Mode" aggregates by band, specific modes show (band, mode) columns
- Backend groups by slot and collects all confirmed QSOs in qsos array
- Frontend displays clickable count links (removed blue bubbles)
Backend changes:
- calculateDOKAwardProgress(): groups by (DOK, band, mode), collects qsos array
- calculatePointsAwardProgress(): updated for all count modes with qsos array
- getAwardEntityBreakdown(): groups by (entity, band, mode) slots
Frontend changes:
- Add mode filter dropdown with "Mixed Mode" default
- Update grouping logic to handle mixed mode vs specific mode
- Replace count badges with simple clickable links
- Add QSO list modal showing all QSOs per slot
- Add Mode column to QSO list (useful in mixed mode)
Co-Authored-By: Claude <noreply@anthropic.com>
Add complete specification document for the JSON-driven award
calculation system. Documents all rule types, filter operators,
QSO schema, and implementation guidance suitable for porting
to any programming language.
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix admin action log not displaying entries (use raw sqlite for self-join)
- Add global impersonation banner to all pages during impersonation
- Fix timestamp display in action log (convert Unix seconds to milliseconds)
- Add loginWithToken method to auth store for direct token authentication
- Fix /api/auth/me to include impersonatedBy field from JWT
- Remove duplicate impersonation code from admin page
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix admin users last-sync showing 1970 instead of actual sync date
- Changed from MAX(qsos.createdAt) to MAX(syncJobs.completedAt)
- Added timestamp conversion (seconds to milliseconds) for proper Date serialization
- Fix logout redirect not working from admin dashboard
- Changed from goto() to window.location.href for hard redirect
- Ensures proper navigation after auth state changes
Co-Authored-By: Claude <noreply@anthropic.com>
Master is now for standalone/run-on-metal deployment only.
Docker-related files moved to dedicated 'docker' branch.
Co-Authored-By: Claude <noreply@anthropic.com>
Remove outdated phase markdown files and optimize.md that are no longer relevant to the active codebase.
Co-Authored-By: Claude <noreply@anthropic.com>
The modals were using selectedUser.userId but the user object has the field
named id, not userId. This caused undefined to be passed to the backend,
resulting in "Invalid user ID" error when trying to impersonate or change
user roles.
Co-Authored-By: Claude <noreply@anthropic.com>
Add new award for confirming 73 unique QSO partners via AO-73 satellite.
Counts unique callsigns confirmed via LoTW with satName filter.
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove role column from users schema (migration 0003)
- Update auth and admin services to use is_admin only
- Remove role from JWT token payloads
- Update admin CLI to use is_admin field
- Update frontend admin page to use isAdmin boolean
- Fix security: remove console.log dumping credentials in settings
Co-Authored-By: Claude <noreply@anthropic.com>
Adds new tables and columns for admin functionality:
- Create admin_actions table for audit logging
- Create qso_changes table for sync job rollback support
- Add role column to users (default: 'user')
- Add is_admin column to users (default: false)
No data loss - uses ALTER TABLE with safe defaults.
Co-Authored-By: Claude <noreply@anthropic.com>
Fixes frontend freeze during large sync operations (8000+ QSOs).
Root cause: Sequential processing with individual database operations
(~24,000 queries for 8000 QSOs) blocked the event loop, preventing
polling requests from being processed.
Changes:
- Process QSOs in batches of 100
- Single SELECT query per batch for duplicate detection
- Batch INSERTs for new QSOs and change tracking
- Add yield points (setImmediate) after each batch to allow
event loop processing of polling requests
Performance: ~98% reduction in database operations
Before: 8000 QSOs × 3 queries = ~24,000 sequential operations
After: 80 batches × ~4 operations = ~320 operations
Co-Authored-By: Claude <noreply@anthropic.com>
- Add admin role system with role and isAdmin fields to users table
- Create admin_actions audit log table for tracking all admin operations
- Implement admin CLI tool for user management (create, promote, demote, list, check)
- Add admin authentication with role-based access control
- Create admin service layer with system statistics and user management
- Implement user impersonation system with proper security checks
- Add admin API endpoints for user management and system statistics
- Create admin dashboard UI with overview, users, and action logs
- Fix admin stats endpoint and user deletion with proper foreign key handling
- Add admin link to navigation bar for admin users
Database:
- Add role and isAdmin columns to users table
- Create admin_actions table for audit trail
- Migration script: add-admin-functionality.js
CLI:
- src/backend/scripts/admin-cli.js - Admin user management tool
Backend:
- src/backend/services/admin.service.js - Admin business logic
- Updated auth.service.js with admin helper functions
- Enhanced index.js with admin routes and middleware
- Export sqlite connection from config for raw SQL operations
Frontend:
- src/frontend/src/routes/admin/+page.svelte - Admin dashboard
- Updated api.js with adminAPI functions
- Added Admin link to navigation bar
Security:
- Admin-only endpoints with role verification
- Audit logging for all admin actions
- Impersonation with 1-hour token expiration
- Foreign key constraint handling for user deletion
- Cannot delete self or other admins
- Last admin protection
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>
- 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>
- 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>
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>
Add a "Recent Sync Jobs" section to the dashboard that displays the last 5 sync jobs with:
- Job type (LoTW/DCL) with icon
- Status badge (pending/running/completed/failed)
- Relative timestamp (e.g., "5m ago", "2h ago")
- Duration for completed jobs
- Sync statistics (total, added, updated, skipped)
- Error messages for failed jobs
- Empty state with helpful CTAs
- Loading state while fetching
Uses existing backend API (GET /api/jobs?limit=5).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add backend logging to logs/backend.log with file rotation support
- Add frontend logging to logs/frontend.log via /api/logs endpoint
- Add frontend logger utility with batching and user context
- Update .gitignore to exclude log files but preserve logs directory
- Update CLAUDE.md with logging documentation and usage examples
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix onResponse error by using onAfterHandle for Elysia framework
- Fix URI malformed errors from browser extensions in Vite dev server
- Update middleware plugin to run before SvelteKit with enforce: 'pre'
- Insert middleware at beginning of stack to catch malformed URIs early
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add a summary row at the bottom of the award details table that shows the sum for each band column. The sum automatically calculates:
- For points-based awards: Total confirmed points per band
- For QSO-based awards: Count of confirmed QSOs per band
The sum row is styled with a gray background and bold text for visual distinction.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add complete Docker configuration for containerized deployment:
- Multi-stage Dockerfile using official Bun runtime
- docker-compose.yml for single-port stack orchestration
- Host-mounted database volume with auto-initialization
- Custom database init script using bun:sqlite
- Entrypoint script for seamless database setup
- Environment configuration template
- Comprehensive DOCKER.md documentation
Key features:
- Single exposed port (3001) serving both API and frontend
- Database persists in ./data directory on host
- Auto-creates database from template on first startup
- Health checks for monitoring
- Architecture-agnostic (works on x86 and ARM64)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>