## 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>
795 lines
20 KiB
Markdown
795 lines
20 KiB
Markdown
# Ham Radio Award Portal - Documentation
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture](#architecture)
|
|
2. [Components](#components)
|
|
3. [Code Structure](#code-structure)
|
|
4. [Awards System](#awards-system)
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Overview
|
|
|
|
The Ham Radio Award Portal is a full-stack web application designed to help amateur radio operators track their award progress by syncing QSOs (contacts) from ARRL's Logbook of the World (LoTW).
|
|
|
|
### Technology Stack
|
|
|
|
**Backend:**
|
|
- **Runtime**: Bun - Fast JavaScript runtime
|
|
- **Framework**: Elysia.js - Lightweight, high-performance web framework
|
|
- **Database**: SQLite - Embedded database for data persistence
|
|
- **ORM**: Drizzle ORM - Type-safe database queries
|
|
- **Authentication**: JWT tokens via `@elysiajs/jwt`
|
|
- **Password Hashing**: bcrypt
|
|
- **Logging**: Pino - Structured logging with timestamps and log levels
|
|
|
|
**Frontend:**
|
|
- **Framework**: SvelteKit - Modern reactive framework
|
|
- **Bundler**: Vite - Fast development server and build tool
|
|
- **Language**: JavaScript with TypeScript type definitions
|
|
- **State Management**: Svelte stores
|
|
- **Styling**: CSS modules
|
|
|
|
### System Architecture
|
|
|
|
```
|
|
┌─────────────────┐ HTTP/REST ┌─────────────────┐
|
|
│ │ ◄──────────────────► │ │
|
|
│ SvelteKit │ │ ElysiaJS │
|
|
│ Frontend │ │ Backend │
|
|
│ │ │ Server │
|
|
└─────────────────┘ └────────┬────────┘
|
|
│
|
|
▼
|
|
┌─────────────────┐
|
|
│ SQLite DB │
|
|
│ (Drizzle ORM) │
|
|
└─────────────────┘
|
|
▲
|
|
│
|
|
┌────────┴────────┐
|
|
│ ARRL LoTW │
|
|
│ External API │
|
|
└─────────────────┘
|
|
```
|
|
|
|
### Key Design Decisions
|
|
|
|
1. **SQLite over PostgreSQL/MySQL**: Simplified deployment, embedded database, excellent for single-user or small-scale deployments
|
|
2. **Bun over Node.js**: Faster startup, better performance, native TypeScript support
|
|
3. **ElysiaJS over Express**: Better TypeScript support, faster performance, modern API design
|
|
4. **SvelteKit over React**: Smaller bundle sizes, better performance, simpler reactivity model
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
### Backend Components
|
|
|
|
#### 1. Server (`src/backend/index.js`)
|
|
|
|
Main entry point that configures and starts the ElysiaJS server.
|
|
|
|
**Key Features:**
|
|
- CORS configuration for cross-origin requests
|
|
- JWT authentication plugin
|
|
- Route handlers for API endpoints
|
|
- Error handling middleware
|
|
|
|
**Routes:**
|
|
- `GET /api/health` - Health check endpoint
|
|
- `POST /api/auth/register` - User registration
|
|
- `POST /api/auth/login` - User login
|
|
- `GET /api/auth/me` - Get current user
|
|
- `PUT /api/auth/lotw-credentials` - Update LoTW credentials
|
|
- `POST /api/lotw/sync` - Sync QSOs from LoTW
|
|
- `GET /api/qsos` - Get QSOs with filtering
|
|
- `GET /api/qsos/stats` - Get QSO statistics
|
|
|
|
#### 2. Database Schema (`src/backend/db/schema/index.js`)
|
|
|
|
Defines the database structure using Drizzle ORM schema builder.
|
|
|
|
**Tables:**
|
|
|
|
- **users**: User accounts, authentication credentials, LoTW credentials
|
|
- **qsos**: Amateur radio contacts in ADIF format with LoTW confirmation data
|
|
- **awards**: Award definitions with JSON rule configurations
|
|
- **award_progress**: Cached award progress for each user
|
|
|
|
#### 3. Services
|
|
|
|
**Auth Service** (`src/backend/services/auth.service.js`)
|
|
- User registration and login
|
|
- Password hashing with bcrypt
|
|
- JWT token generation and validation
|
|
- User profile management
|
|
|
|
**LoTW Service** (`src/backend/services/lotw.service.js`)
|
|
- Synchronization with ARRL's Logbook of the World
|
|
- ADIF format parsing
|
|
- Long-polling for report generation
|
|
- QSO deduplication
|
|
- Band/mode normalization
|
|
- Error handling and retry logic
|
|
|
|
#### 4. Configuration (`src/backend/config/`)
|
|
|
|
- **database.js**: Database connection and client initialization
|
|
- **jwt.js**: JWT secret configuration
|
|
- **logger.js**: Pino logger configuration with structured logging and timestamps
|
|
|
|
### Frontend Components
|
|
|
|
#### 1. Pages (SvelteKit Routes)
|
|
|
|
- **`/`**: Dashboard with welcome message
|
|
- **`/auth/login`**: User login form
|
|
- **`/auth/register`**: User registration form
|
|
- **`/qsos`**: QSO logbook with filtering and LoTW sync
|
|
- **`/settings`**: LoTW credentials management
|
|
|
|
#### 2. Layout (`+layout.svelte`)
|
|
|
|
Global layout component providing:
|
|
- **Navigation bar**: Shows user's callsign, navigation links (Dashboard, QSOs, Settings), and logout button
|
|
- Only visible when user is logged in
|
|
- Responsive design with dark theme matching footer
|
|
|
|
#### 3. Libraries
|
|
|
|
**API Client** (`src/frontend/src/lib/api.js`)
|
|
- Centralized API communication
|
|
- Automatic JWT token injection
|
|
- Response/error handling
|
|
- Typed API methods
|
|
|
|
**Stores** (`src/frontend/src/lib/stores.js`)
|
|
- Authentication state management
|
|
- Persistent login with localStorage
|
|
- Reactive user data
|
|
|
|
**Types** (`src/frontend/src/lib/types/`)
|
|
- TypeScript type definitions
|
|
- Award system types
|
|
- API response types
|
|
|
|
---
|
|
|
|
## Code Structure
|
|
|
|
### Directory Layout
|
|
|
|
```
|
|
award/
|
|
├── award-definitions/ # Award rule definitions (JSON)
|
|
│ ├── dxcc.json # DXCC award configurations
|
|
│ ├── dxcc-cw.json # DXCC CW-specific award
|
|
│ ├── was.json # WAS award configurations
|
|
│ ├── vucc-sat.json # VUCC Satellite award
|
|
│ └── sat-rs44.json # Satellite RS-44 award
|
|
│
|
|
├── drizzle/ # Database migrations
|
|
│ └── 0000_init.sql # Initial schema
|
|
│
|
|
├── docs/ # Documentation
|
|
│ └── DOCUMENTATION.md # This file
|
|
│
|
|
├── src/
|
|
│ ├── backend/ # Backend server code
|
|
│ │ ├── config/
|
|
│ │ │ ├── database.js # Database connection
|
|
│ │ │ ├── jwt.js # JWT configuration
|
|
│ │ │ └── logger.js # Pino logging configuration
|
|
│ │ ├── db/
|
|
│ │ │ └── schema/
|
|
│ │ │ └── index.js # Drizzle schema definitions
|
|
│ │ ├── services/
|
|
│ │ │ ├── auth.service.js
|
|
│ │ │ ├── job-queue.service.js
|
|
│ │ │ └── lotw.service.js
|
|
│ │ └── index.js # Main server entry point
|
|
│ │
|
|
│ ├── frontend/ # Frontend application
|
|
│ │ ├── src/
|
|
│ │ │ ├── lib/
|
|
│ │ │ │ ├── api.js # API client
|
|
│ │ │ │ ├── stores.js # Svelte stores
|
|
│ │ │ │ └── types/ # TypeScript definitions
|
|
│ │ │ └── routes/ # SvelteKit pages
|
|
│ │ │ ├── +page.svelte
|
|
│ │ │ ├── auth/
|
|
│ │ │ ├── qsos/
|
|
│ │ │ └── settings/
|
|
│ │ ├── static/ # Static assets
|
|
│ │ └── vite.config.js
|
|
│ │
|
|
│ └── shared/ # Shared TypeScript types
|
|
│ └── index.ts
|
|
│
|
|
├── award.db # SQLite database file
|
|
├── drizzle.config.ts # Drizzle configuration
|
|
├── package.json # Dependencies and scripts
|
|
├── CLAUDE.md # Claude AI instructions
|
|
└── README.md # Project overview
|
|
```
|
|
|
|
### File Organization Principles
|
|
|
|
1. **Separation of Concerns**: Clear separation between backend and frontend
|
|
2. **Shared Types**: Common types in `src/shared/` to ensure consistency
|
|
3. **Service Layer**: Business logic isolated in service files
|
|
4. **Configuration**: Config files co-located in dedicated directories
|
|
5. **Database-First**: Schema drives the application structure
|
|
|
|
### Database Schema Details
|
|
|
|
#### Users Table
|
|
```javascript
|
|
{
|
|
id: integer (primary key, auto-increment)
|
|
email: text (unique, not null)
|
|
passwordHash: text (not null)
|
|
callsign: text (not null)
|
|
lotwUsername: text (nullable)
|
|
lotwPassword: text (nullable, encrypted)
|
|
createdAt: timestamp
|
|
updatedAt: timestamp
|
|
}
|
|
```
|
|
|
|
#### QSOs Table
|
|
```javascript
|
|
{
|
|
id: integer (primary key, auto-increment)
|
|
userId: integer (foreign key → users.id)
|
|
callsign: text
|
|
qsoDate: text (ADIF format: YYYYMMDD)
|
|
timeOn: text (HHMMSS)
|
|
band: text (160m, 80m, 40m, 30m, 20m, 17m, 15m, 12m, 10m, 6m, 2m, 70cm)
|
|
mode: text (CW, SSB, FM, AM, FT8, FT4, PSK31, etc.)
|
|
freq: integer (Hz)
|
|
freqRx: integer (Hz, satellite only)
|
|
entity: text (DXCC entity name)
|
|
entityId: integer (DXCC entity number)
|
|
grid: text (Maidenhead grid square)
|
|
gridSource: text (LOTW, USER, CALC)
|
|
continent: text (NA, SA, EU, AF, AS, OC, AN)
|
|
cqZone: integer
|
|
ituZone: integer
|
|
state: text (US state, CA province, etc.)
|
|
county: text
|
|
satName: text (satellite name)
|
|
satMode: text (satellite mode)
|
|
lotwQslRdate: text (LoTW confirmation date)
|
|
lotwQslRstatus: text ('Y', 'N', '?')
|
|
lotwSyncedAt: timestamp
|
|
createdAt: timestamp
|
|
}
|
|
```
|
|
|
|
#### Awards Table
|
|
```javascript
|
|
{
|
|
id: text (primary key)
|
|
name: text
|
|
description: text
|
|
definition: text (JSON)
|
|
isActive: boolean
|
|
createdAt: timestamp
|
|
}
|
|
```
|
|
|
|
#### Award Progress Table
|
|
```javascript
|
|
{
|
|
id: integer (primary key, auto-increment)
|
|
userId: integer (foreign key → users.id)
|
|
awardId: text (foreign key → awards.id)
|
|
workedCount: integer
|
|
confirmedCount: integer
|
|
totalRequired: integer
|
|
workedEntities: text (JSON array)
|
|
confirmedEntities: text (JSON array)
|
|
lastCalculatedAt: timestamp
|
|
lastQsoSyncAt: timestamp
|
|
updatedAt: timestamp
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Awards System
|
|
|
|
### Overview
|
|
|
|
The awards system is designed to be flexible and extensible. Awards are defined as JSON configuration files that specify rules for calculating progress based on QSOs.
|
|
|
|
### Award Definition Structure
|
|
|
|
Each award is defined with the following structure:
|
|
|
|
```json
|
|
{
|
|
"id": "unique-identifier",
|
|
"name": "Award Display Name",
|
|
"description": "Award description",
|
|
"category": "award-category",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "dxcc|state|grid|...",
|
|
"target": 100,
|
|
"filters": {
|
|
"operator": "AND|OR",
|
|
"filters": [...]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Entity Types
|
|
|
|
| Entity Type | Description | Example Field |
|
|
|-------------|-------------|---------------|
|
|
| `dxcc` | DXCC entities (countries) | `entityId` |
|
|
| `state` | US States, Canadian provinces | `state` |
|
|
| `grid` | Maidenhead grid squares | `grid` |
|
|
|
|
### Filter Operators
|
|
|
|
| Operator | Description |
|
|
|----------|-------------|
|
|
| `eq` | Equal to |
|
|
| `ne` | Not equal to |
|
|
| `in` | In list |
|
|
| `nin` | Not in list |
|
|
| `contains` | Contains substring |
|
|
| `AND` | All filters must match |
|
|
| `OR` | At least one filter must match |
|
|
|
|
### Award Categories
|
|
|
|
- **`dxcc`**: DXCC (Countries/Districts) awards
|
|
- **`was`**: Worked All States awards
|
|
- **`vucc`**: VHF/UHF Century Club awards
|
|
- **`satellite`**: Satellite-specific awards
|
|
|
|
---
|
|
|
|
## Award Examples
|
|
|
|
### 1. DXCC Mixed Mode
|
|
|
|
**File**: `award-definitions/dxcc.json`
|
|
|
|
```json
|
|
{
|
|
"id": "dxcc-mixed",
|
|
"name": "DXCC Mixed Mode",
|
|
"description": "Confirm 100 DXCC entities on any band/mode",
|
|
"category": "dxcc",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "dxcc",
|
|
"target": 100
|
|
}
|
|
}
|
|
```
|
|
|
|
**Explanation:**
|
|
- Counts unique DXCC entities (`entityId` field)
|
|
- No filtering - all QSOs count toward progress
|
|
- Target: 100 confirmed entities
|
|
- Both worked (any QSO) and confirmed (LoTW QSL) are tracked
|
|
|
|
**Progress Calculation:**
|
|
```javascript
|
|
worked = COUNT(DISTINCT entityId WHERE lotwQslRstatus IS NOT NULL)
|
|
confirmed = COUNT(DISTINCT entityId WHERE lotwQslRstatus = 'Y')
|
|
```
|
|
|
|
---
|
|
|
|
### 2. DXCC CW (CW-specific)
|
|
|
|
**File**: `award-definitions/dxcc-cw.json`
|
|
|
|
```json
|
|
{
|
|
"id": "dxcc-cw",
|
|
"name": "DXCC CW",
|
|
"description": "Confirm 100 DXCC entities using CW mode only",
|
|
"category": "dxcc",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "dxcc",
|
|
"target": 100,
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{
|
|
"field": "mode",
|
|
"operator": "eq",
|
|
"value": "CW"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Explanation:**
|
|
- Counts unique DXCC entities
|
|
- Only QSOs with `mode = 'CW'` are counted
|
|
- Target: 100 confirmed entities
|
|
- Example: A CW QSO with Germany (entityId=230) counts, but SSB does not
|
|
|
|
**Progress Calculation:**
|
|
```javascript
|
|
worked = COUNT(DISTINCT entityId
|
|
WHERE mode = 'CW' AND lotwQslRstatus IS NOT NULL)
|
|
confirmed = COUNT(DISTINCT entityId
|
|
WHERE mode = 'CW' AND lotwQslRstatus = 'Y')
|
|
```
|
|
|
|
---
|
|
|
|
### 3. WAS Mixed Mode
|
|
|
|
**File**: `award-definitions/was.json`
|
|
|
|
```json
|
|
{
|
|
"id": "was-mixed",
|
|
"name": "WAS Mixed Mode",
|
|
"description": "Confirm all 50 US states",
|
|
"category": "was",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "state",
|
|
"target": 50,
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{
|
|
"field": "entity",
|
|
"operator": "eq",
|
|
"value": "United States"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Explanation:**
|
|
- Counts unique US states
|
|
- Only QSOs where `entity = 'United States'`
|
|
- Target: 50 confirmed states
|
|
- Both worked and confirmed are tracked separately
|
|
|
|
**Progress Calculation:**
|
|
```javascript
|
|
worked = COUNT(DISTINCT state
|
|
WHERE entity = 'United States' AND lotwQslRstatus IS NOT NULL)
|
|
confirmed = COUNT(DISTINCT state
|
|
WHERE entity = 'United States' AND lotwQslRstatus = 'Y')
|
|
```
|
|
|
|
**Example States:**
|
|
- `"CA"` - California
|
|
- `"TX"` - Texas
|
|
- `"NY"` - New York
|
|
- (All 50 US states)
|
|
|
|
---
|
|
|
|
### 4. VUCC Satellite
|
|
|
|
**File**: `award-definitions/vucc-sat.json`
|
|
|
|
```json
|
|
{
|
|
"id": "vucc-satellite",
|
|
"name": "VUCC Satellite",
|
|
"description": "Confirm 100 unique grid squares via satellite",
|
|
"category": "vucc",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "grid",
|
|
"target": 100,
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{
|
|
"field": "satellite",
|
|
"operator": "eq",
|
|
"value": true
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Explanation:**
|
|
- Counts unique grid squares (4-character: FN31, CM97, etc.)
|
|
- Only satellite QSOs (where `satName` is not null)
|
|
- Target: 100 confirmed grids
|
|
- Grid squares are counted independently of band/mode
|
|
|
|
**Progress Calculation:**
|
|
```javascript
|
|
worked = COUNT(DISTINCT SUBSTRING(grid, 1, 4)
|
|
WHERE satName IS NOT NULL AND lotwQslRstatus IS NOT NULL)
|
|
confirmed = COUNT(DISTINCT SUBSTRING(grid, 1, 4)
|
|
WHERE satName IS NOT NULL AND lotwQslRstatus = 'Y')
|
|
```
|
|
|
|
**Example Grids:**
|
|
- `"FN31pr"` → counts as `"FN31"`
|
|
- `"CM97"` → counts as `"CM97"`
|
|
|
|
---
|
|
|
|
### 5. Satellite RS-44 (Specific Satellite)
|
|
|
|
**File**: `award-definitions/sat-rs44.json`
|
|
|
|
```json
|
|
{
|
|
"id": "sat-rs44",
|
|
"name": "RS-44 Satellite Award",
|
|
"description": "Make 100 QSOs via RS-44 satellite",
|
|
"category": "satellite",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "callsign",
|
|
"target": 100,
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{
|
|
"field": "satName",
|
|
"operator": "eq",
|
|
"value": "RS-44"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Explanation:**
|
|
- Counts unique callsigns worked via RS-44 satellite
|
|
- Only QSOs where `satName = 'RS-44'`
|
|
- Target: 100 unique callsigns
|
|
- Each station (callsign) counts once regardless of multiple QSOs
|
|
|
|
**Progress Calculation:**
|
|
```javascript
|
|
worked = COUNT(DISTINCT callsign
|
|
WHERE satName = 'RS-44' AND lotwQslRstatus IS NOT NULL)
|
|
confirmed = COUNT(DISTINCT callsign
|
|
WHERE satName = 'RS-44' AND lotwQslRstatus = 'Y')
|
|
```
|
|
|
|
---
|
|
|
|
### Advanced Filter Examples
|
|
|
|
#### Multiple Conditions (AND)
|
|
|
|
Count only 20m CW QSOs:
|
|
|
|
```json
|
|
{
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{ "field": "band", "operator": "eq", "value": "20m" },
|
|
{ "field": "mode", "operator": "eq", "value": "CW" }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Multiple Options (OR)
|
|
|
|
Count any phone mode QSOs:
|
|
|
|
```json
|
|
{
|
|
"filters": {
|
|
"operator": "OR",
|
|
"filters": [
|
|
{ "field": "mode", "operator": "eq", "value": "SSB" },
|
|
{ "field": "mode", "operator": "eq", "value": "FM" },
|
|
{ "field": "mode", "operator": "eq", "value": "AM" }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Band Ranges
|
|
|
|
Count HF QSOs (all HF bands):
|
|
|
|
```json
|
|
{
|
|
"filters": {
|
|
"operator": "OR",
|
|
"filters": [
|
|
{ "field": "band", "operator": "eq", "value": "160m" },
|
|
{ "field": "band", "operator": "eq", "value": "80m" },
|
|
{ "field": "band", "operator": "eq", "value": "60m" },
|
|
{ "field": "band", "operator": "eq", "value": "40m" },
|
|
{ "field": "band", "operator": "eq", "value": "30m" },
|
|
{ "field": "band", "operator": "eq", "value": "20m" },
|
|
{ "field": "band", "operator": "eq", "value": "17m" },
|
|
{ "field": "band", "operator": "eq", "value": "15m" },
|
|
{ "field": "band", "operator": "eq", "value": "12m" },
|
|
{ "field": "band", "operator": "eq", "value": "10m" }
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Grid Square by Continent
|
|
|
|
Count VUCC grids in Europe only:
|
|
|
|
```json
|
|
{
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{ "field": "continent", "operator": "eq", "value": "EU" },
|
|
{
|
|
"filters": [
|
|
{ "field": "band", "operator": "eq", "value": "2m" },
|
|
{ "field": "band", "operator": "eq", "value": "70cm" }
|
|
],
|
|
"operator": "OR"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Creating Custom Awards
|
|
|
|
To create a new award:
|
|
|
|
1. **Create a new JSON file** in `award-definitions/`
|
|
2. **Define the award structure** following the schema above
|
|
3. **Add to database** (via API or database migration)
|
|
|
|
### Example: IOTA Award
|
|
|
|
```json
|
|
{
|
|
"id": "iota-mixed",
|
|
"name": "IOTA Mixed Mode",
|
|
"description": "Confirm 100 Islands on the Air",
|
|
"category": "iota",
|
|
"rules": {
|
|
"type": "entity",
|
|
"entityType": "iota",
|
|
"target": 100,
|
|
"filters": {
|
|
"operator": "AND",
|
|
"filters": [
|
|
{ "field": "iotaNumber", "operator": "ne", "value": null }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note**: This would require adding an `iotaNumber` field to the QSO table.
|
|
|
|
---
|
|
|
|
## Award Progress API
|
|
|
|
### Get User Award Progress
|
|
|
|
```
|
|
GET /api/awards/progress
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"userId": 1,
|
|
"awards": [
|
|
{
|
|
"id": "dxcc-mixed",
|
|
"name": "DXCC Mixed Mode",
|
|
"worked": 87,
|
|
"confirmed": 73,
|
|
"totalRequired": 100,
|
|
"percentage": 73
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Get Detailed Award Breakdown
|
|
|
|
```
|
|
GET /api/awards/progress/:awardId
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"awardId": "dxcc-mixed",
|
|
"workedEntities": [
|
|
{ "entity": "Germany", "entityId": 230, "worked": true, "confirmed": true },
|
|
{ "entity": "Japan", "entityId": 339, "worked": true, "confirmed": false }
|
|
],
|
|
"confirmedEntities": [
|
|
{ "entity": "Germany", "entityId": 230 }
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
The award system is designed for extensibility. Planned enhancements:
|
|
|
|
1. **Additional Entity Types**:
|
|
- IOTA (Islands on the Air)
|
|
- CQ Zones
|
|
- ITU Zones
|
|
- Counties
|
|
|
|
2. **Advanced Filters**:
|
|
- Date ranges
|
|
- Regular expressions
|
|
- Custom field queries
|
|
|
|
3. **Award Endorsements**:
|
|
- Band-specific endorsements
|
|
- Mode-specific endorsements
|
|
- Combined endorsements
|
|
|
|
4. **Award Tiers**:
|
|
- Bronze, Silver, Gold levels
|
|
- Progressive achievements
|
|
|
|
5. **User-Defined Awards**:
|
|
- Allow users to create custom awards
|
|
- Share award definitions with community
|
|
|
|
---
|
|
|
|
## Contributing
|
|
|
|
When adding new awards or modifying the award system:
|
|
|
|
1. Follow the JSON schema for award definitions
|
|
2. Add tests for new filter types
|
|
3. Update this documentation with examples
|
|
4. Ensure database schema supports required fields
|
|
5. Test progress calculations thoroughly
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [ARRL LoTW](https://lotw.arrl.org/)
|
|
- [ADIF Specification](https://adif.org/)
|
|
- [DXCC List](https://www.arrl.org/dxcc)
|
|
- [VUCC Program](https://www.arrl.org/vucc)
|
|
- [WAS Award](https://www.arrl.org/was)
|