feat: Single-port deployment with improved error handling and SvelteKit static build

- Frontend now uses @sveltejs/adapter-static for production builds
- Backend serves both API and static files from single port (originally port 3000)
- Removed all throw statements from services to avoid Elysia prototype errors
- Fixed favicon serving and SvelteKit assets path handling
- Added ecosystem.config.js for PM2 process management
- Comprehensive deployment documentation (PM2 + HAProxy)
- Updated README with single-port architecture
- Created start.sh script for easy production start
This commit is contained in:
2026-01-16 13:58:03 +01:00
parent 413a6e9831
commit 907dc48f1b
12 changed files with 683 additions and 132 deletions

View File

@@ -84,7 +84,9 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
for (let attempt = 0; attempt < POLLING_CONFIG.maxRetries; attempt++) {
const elapsed = Date.now() - startTime;
if (elapsed > POLLING_CONFIG.maxTotalTime) {
throw new Error(`LoTW sync timeout: exceeded maximum wait time of ${POLLING_CONFIG.maxTotalTime / 1000} seconds`);
return {
error: `LoTW sync timeout: exceeded maximum wait time of ${POLLING_CONFIG.maxTotalTime / 1000} seconds`
};
}
if (attempt > 0) {
@@ -104,9 +106,9 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
await sleep(POLLING_CONFIG.retryDelay);
continue;
} else if (response.status === 401) {
throw new Error('Invalid LoTW credentials. Please check your username and password in Settings.');
return { error: 'Invalid LoTW credentials. Please check your username and password in Settings.' };
} else if (response.status === 404) {
throw new Error('LoTW service not found (404). The LoTW API URL may have changed.');
return { error: 'LoTW service not found (404). The LoTW API URL may have changed.' };
} else {
logger.warn(`LoTW returned ${response.status}, retrying...`);
await sleep(POLLING_CONFIG.retryDelay);
@@ -117,7 +119,7 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
const adifData = await response.text();
if (adifData.toLowerCase().includes('username/password incorrect')) {
throw new Error('Username/password incorrect');
return { error: 'Username/password incorrect' };
}
const header = adifData.trim().substring(0, 39).toLowerCase();
@@ -127,7 +129,8 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
await sleep(POLLING_CONFIG.retryDelay);
continue;
}
throw new Error('Downloaded LoTW report is invalid. Check your credentials.');
return { error: 'Downloaded LoTW report is invalid. Check your credentials.' };
}
logger.info('LoTW report downloaded successfully', { size: adifData.length });
@@ -159,7 +162,9 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
}
const totalTime = Math.round((Date.now() - startTime) / 1000);
throw new Error(`LoTW sync failed: Report not ready after ${POLLING_CONFIG.maxRetries} attempts (${totalTime}s). LoTW may be experiencing high load. Please try again later.`);
return {
error: `LoTW sync failed: Report not ready after ${POLLING_CONFIG.maxRetries} attempts (${totalTime}s). LoTW may be experiencing high load. Please try again later.`
};
}
/**