d959235cdd22099613c5ac5461384b4c11adc57e
## 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>
Ham Radio Award Portal
A web application for amateur radio operators to track QSOs (contacts) and award progress using Logbook of the World (LoTW) data.
Features
- User Authentication: Register and login with callsign, email, and password
- LoTW Integration: Sync QSOs from ARRL's Logbook of the World
- Background job queue for non-blocking sync operations
- Incremental sync using last confirmation date
- Wavelog-compatible download logic with proper validation
- One sync job per user enforcement
- QSO Log: View and manage confirmed QSOs
- Pagination support for large QSO collections
- Filter by band, mode, and confirmation status
- Statistics dashboard (total QSOs, confirmed, DXCC entities, bands)
- Delete all QSOs with confirmation
- Settings: Configure LoTW credentials securely
Tech Stack
Backend
- Runtime: Bun
- Framework: Elysia.js
- Database: SQLite with Drizzle ORM
- Authentication: JWT tokens
- Logging: Pino with structured logging and timestamps
Frontend
- Framework: SvelteKit
- Language: JavaScript
- Styling: Custom CSS
Project Structure
award/
├── src/
│ ├── backend/
│ │ ├── config/
│ │ │ ├── database.js # Database connection
│ │ │ ├── jwt.js # JWT configuration
│ │ │ └── logger.js # Pino logging configuration
│ │ ├── db/
│ │ │ └── schema/
│ │ │ └── index.js # Database schema (users, qsos, sync_jobs)
│ │ ├── services/
│ │ │ ├── auth.service.js # User authentication
│ │ │ ├── lotw.service.js # LoTW sync & QSO management
│ │ │ └── job-queue.service.js # Background job queue
│ │ └── index.js # API routes and server
│ └── frontend/
│ ├── src/
│ │ ├── lib/
│ │ │ ├── api.js # API client
│ │ │ └── stores.js # Svelte stores (auth)
│ │ └── routes/
│ │ ├── +layout.svelte # Navigation bar & layout
│ │ ├── +page.svelte # Dashboard
│ │ ├── auth/
│ │ │ ├── login/+page.svelte # Login page
│ │ │ └── register/+page.svelte # Registration page
│ │ ├── qsos/+page.svelte # QSO log with pagination
│ │ └── settings/+page.svelte # Settings & LoTW credentials
│ └── package.json
├── award.db # SQLite database (auto-created)
├── drizzle.config.js # Drizzle ORM configuration
├── package.json
└── README.md
Setup
Prerequisites
- Bun v1.3.6 or later
Installation
- Clone the repository:
git clone <repository-url>
cd award
- Install dependencies:
bun install
- Set up environment variables (optional):
Create a
.envfile in the project root:
JWT_SECRET=your-secret-key-here
If not provided, a default secret will be used.
- Initialize the database:
bun run db:push
This creates the SQLite database with required tables (users, qsos, sync_jobs).
Running the Application
Start both backend and frontend:
# Start backend (port 3001)
bun run backend/index.js
# Start frontend (port 5173)
cd src/frontend && bun run dev
Or use the convenience scripts:
# Backend only
bun run backend
# Frontend only
bun run frontend
The application will be available at:
- Frontend: http://localhost:5173
- Backend API: http://localhost:3001
API Endpoints
Authentication
POST /api/auth/register- Register new userPOST /api/auth/login- Login userGET /api/auth/me- Get current user profilePUT /api/auth/lotw-credentials- Update LoTW credentials
LoTW Sync
POST /api/lotw/sync- Queue a LoTW sync job (returns job ID)
Jobs
GET /api/jobs/:jobId- Get job statusGET /api/jobs/active- Get user's active jobGET /api/jobs- Get recent jobs (query:?limit=10)
QSOs
GET /api/qsos- Get user's QSOs with pagination- Query parameters:
?page=1&limit=100&band=20m&mode=CW&confirmed=true
- Query parameters:
GET /api/qsos/stats- Get QSO statisticsDELETE /api/qsos/all- Delete all QSOs (requires confirmation)
Health
GET /api/health- Health check endpoint
Database Schema
Users Table
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
callsign TEXT NOT NULL,
lotwUsername TEXT,
lotwPassword TEXT,
createdAt TEXT NOT NULL
);
QSOs Table
CREATE TABLE qsos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER NOT NULL,
callsign TEXT NOT NULL,
qsoDate TEXT NOT NULL,
timeOn TEXT NOT NULL,
band TEXT,
mode TEXT,
entity TEXT,
grid TEXT,
lotwQslRstatus TEXT,
lotwQslRdate TEXT,
FOREIGN KEY (userId) REFERENCES users(id)
);
Sync Jobs Table
CREATE TABLE sync_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER NOT NULL,
status TEXT NOT NULL, -- pending, running, completed, failed
type TEXT NOT NULL, -- lotw_sync
startedAt INTEGER,
completedAt INTEGER,
result TEXT, -- JSON
error TEXT,
createdAt INTEGER NOT NULL,
FOREIGN KEY (userId) REFERENCES users(id)
);
Features in Detail
Background Job Queue
The application uses an in-memory job queue system for async operations:
- Jobs are persisted to database for recovery
- Only one active job per user (enforced at queue level)
- Status tracking: pending → running → completed/failed
- Real-time progress updates via job result field
- Client polls job status every 2 seconds
LoTW Sync Logic
Following Wavelog's proven approach:
- First sync: Uses date
2000-01-01to retrieve all QSOs - Subsequent syncs: Uses
MAX(lotwQslRdate)from database - Validation:
- Checks for "Username/password incorrect" in response
- Validates file starts with "ARRL Logbook of the World Status Report"
- Timeout handling: 30-second connection timeout
- Query parameters: Matches Wavelog's LoTW download
Pagination
- Default page size: 100 QSOs per page
- Supports custom page size via
limitparameter - Shows page numbers with ellipsis for large page counts
- Displays "Showing X-Y of Z" info
- Previous/Next navigation buttons
Development
Database Migrations
# Push schema changes to database
bun run db:push
# Open Drizzle Studio (database GUI)
bun run db:studio
Linting
bun run lint
License
MIT
Credits
Description
Languages
Svelte
53.2%
JavaScript
45.4%
CSS
0.8%
HTML
0.5%