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>
This commit is contained in:
@@ -8,6 +8,11 @@ import {
|
||||
getUserById,
|
||||
updateLoTWCredentials,
|
||||
} from './services/auth.service.js';
|
||||
import {
|
||||
syncQSOs,
|
||||
getUserQSOs,
|
||||
getQSOStats,
|
||||
} from './services/lotw.service.js';
|
||||
|
||||
/**
|
||||
* Main backend application
|
||||
@@ -179,7 +184,17 @@ const app = new Elysia()
|
||||
}
|
||||
|
||||
try {
|
||||
await updateLoTWCredentials(user.id, body.lotwUsername, body.lotwPassword);
|
||||
// Get current user data to preserve password if not provided
|
||||
const userData = await getUserById(user.id);
|
||||
if (!userData) {
|
||||
set.status = 404;
|
||||
return { success: false, error: 'User not found' };
|
||||
}
|
||||
|
||||
// If password is empty, keep existing password
|
||||
const lotwPassword = body.lotwPassword || userData.lotwPassword;
|
||||
|
||||
await updateLoTWCredentials(user.id, body.lotwUsername, lotwPassword);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -196,11 +211,106 @@ const app = new Elysia()
|
||||
{
|
||||
body: t.Object({
|
||||
lotwUsername: t.String(),
|
||||
lotwPassword: t.String(),
|
||||
lotwPassword: t.Optional(t.String()),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* POST /api/lotw/sync
|
||||
* Sync QSOs from LoTW (requires authentication)
|
||||
*/
|
||||
.post('/api/lotw/sync', async ({ user, set }) => {
|
||||
if (!user) {
|
||||
set.status = 401;
|
||||
return { success: false, error: 'Unauthorized' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Get user's LoTW credentials from database
|
||||
const userData = await getUserById(user.id);
|
||||
if (!userData || !userData.lotwUsername || !userData.lotwPassword) {
|
||||
set.status = 400;
|
||||
return {
|
||||
success: false,
|
||||
error: 'LoTW credentials not configured. Please add them in Settings.',
|
||||
};
|
||||
}
|
||||
|
||||
// Decrypt password (for now, assuming it's stored as-is. TODO: implement encryption)
|
||||
const lotwPassword = userData.lotwPassword;
|
||||
|
||||
// Sync QSOs from LoTW
|
||||
const result = await syncQSOs(user.id, userData.lotwUsername, lotwPassword);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
error: `LoTW sync failed: ${error.message}`,
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* GET /api/qsos
|
||||
* Get user's QSOs (requires authentication)
|
||||
*/
|
||||
.get('/api/qsos', async ({ user, query, set }) => {
|
||||
if (!user) {
|
||||
set.status = 401;
|
||||
return { success: false, error: 'Unauthorized' };
|
||||
}
|
||||
|
||||
try {
|
||||
const filters = {};
|
||||
if (query.band) filters.band = query.band;
|
||||
if (query.mode) filters.mode = query.mode;
|
||||
if (query.confirmed) filters.confirmed = query.confirmed === 'true';
|
||||
|
||||
const qsos = await getUserQSOs(user.id, filters);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
qsos,
|
||||
count: qsos.length,
|
||||
};
|
||||
} catch (error) {
|
||||
set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to fetch QSOs',
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* GET /api/qsos/stats
|
||||
* Get QSO statistics (requires authentication)
|
||||
*/
|
||||
.get('/api/qsos/stats', async ({ user, set }) => {
|
||||
if (!user) {
|
||||
set.status = 401;
|
||||
return { success: false, error: 'Unauthorized' };
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await getQSOStats(user.id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
stats,
|
||||
};
|
||||
} catch (error) {
|
||||
set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to fetch statistics',
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
// Health check endpoint
|
||||
.get('/api/health', () => ({
|
||||
status: 'ok',
|
||||
|
||||
Reference in New Issue
Block a user