## 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>
260 lines
6.9 KiB
Markdown
260 lines
6.9 KiB
Markdown
# 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](https://bun.sh) v1.3.6 or later
|
|
|
|
### Installation
|
|
|
|
1. Clone the repository:
|
|
```bash
|
|
git clone <repository-url>
|
|
cd award
|
|
```
|
|
|
|
2. Install dependencies:
|
|
```bash
|
|
bun install
|
|
```
|
|
|
|
3. Set up environment variables (optional):
|
|
Create a `.env` file in the project root:
|
|
```env
|
|
JWT_SECRET=your-secret-key-here
|
|
```
|
|
|
|
If not provided, a default secret will be used.
|
|
|
|
4. Initialize the database:
|
|
```bash
|
|
bun run db:push
|
|
```
|
|
|
|
This creates the SQLite database with required tables (users, qsos, sync_jobs).
|
|
|
|
## Running the Application
|
|
|
|
Start both backend and frontend:
|
|
|
|
```bash
|
|
# Start backend (port 3001)
|
|
bun run backend/index.js
|
|
|
|
# Start frontend (port 5173)
|
|
cd src/frontend && bun run dev
|
|
```
|
|
|
|
Or use the convenience scripts:
|
|
```bash
|
|
# 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 user
|
|
- `POST /api/auth/login` - Login user
|
|
- `GET /api/auth/me` - Get current user profile
|
|
- `PUT /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 status
|
|
- `GET /api/jobs/active` - Get user's active job
|
|
- `GET /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`
|
|
- `GET /api/qsos/stats` - Get QSO statistics
|
|
- `DELETE /api/qsos/all` - Delete all QSOs (requires confirmation)
|
|
|
|
### Health
|
|
- `GET /api/health` - Health check endpoint
|
|
|
|
## Database Schema
|
|
|
|
### Users Table
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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:
|
|
1. **First sync**: Uses date `2000-01-01` to retrieve all QSOs
|
|
2. **Subsequent syncs**: Uses `MAX(lotwQslRdate)` from database
|
|
3. **Validation**:
|
|
- Checks for "Username/password incorrect" in response
|
|
- Validates file starts with "ARRL Logbook of the World Status Report"
|
|
4. **Timeout handling**: 30-second connection timeout
|
|
5. **Query parameters**: Matches Wavelog's LoTW download
|
|
|
|
### Pagination
|
|
|
|
- Default page size: 100 QSOs per page
|
|
- Supports custom page size via `limit` parameter
|
|
- Shows page numbers with ellipsis for large page counts
|
|
- Displays "Showing X-Y of Z" info
|
|
- Previous/Next navigation buttons
|
|
|
|
## Development
|
|
|
|
### Database Migrations
|
|
|
|
```bash
|
|
# Push schema changes to database
|
|
bun run db:push
|
|
|
|
# Open Drizzle Studio (database GUI)
|
|
bun run db:studio
|
|
```
|
|
|
|
### Linting
|
|
|
|
```bash
|
|
bun run lint
|
|
```
|
|
|
|
## License
|
|
|
|
MIT
|
|
|
|
## Credits
|
|
|
|
- LoTW integration inspired by [Wavelog](https://github.com/magicbug/CloudLog)
|
|
- Built with [Bun](https://bun.sh), [Elysia](https://elysiajs.com), and [SvelteKit](https://kit.svelte.dev)
|