chore: code cleanup - remove duplicates and add caching

- Delete duplicate getCacheStats() function in cache.service.js
- Fix date calculation bug in lotw.service.js (was Date.now()-Date.now())
- Extract duplicate helper functions (yieldToEventLoop, getQSOKey) to sync-helpers.js
- Cache award definitions in memory to avoid repeated file I/O
- Delete unused parseDCLJSONResponse() function
- Remove unused imports (getPerformanceSummary, resetPerformanceMetrics)
- Auto-discover award JSON files instead of hardcoded list

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-01-22 10:22:00 +01:00
parent d9e0e462b9
commit cce520a00e
5 changed files with 45 additions and 81 deletions

View File

@@ -1,7 +1,7 @@
import { db, logger } from '../config.js';
import { qsos } from '../db/schema/index.js';
import { eq, and, or, desc, sql } from 'drizzle-orm';
import { readFileSync } from 'fs';
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { getCachedAwardProgress, setCachedAwardProgress } from './cache.service.js';
@@ -13,23 +13,25 @@ import { getCachedAwardProgress, setCachedAwardProgress } from './cache.service.
// Load award definitions from files
const AWARD_DEFINITIONS_DIR = join(process.cwd(), 'award-definitions');
// In-memory cache for award definitions (static, never changes at runtime)
let cachedAwardDefinitions = null;
/**
* Load all award definitions
* Load all award definitions (cached in memory)
*/
function loadAwardDefinitions() {
// Return cached definitions if available
if (cachedAwardDefinitions) {
return cachedAwardDefinitions;
}
const definitions = [];
try {
const files = [
'dxcc.json',
'dxcc-sat.json',
'was.json',
'vucc-sat.json',
'sat-rs44.json',
'special-stations.json',
'dld.json',
'73-on-73.json',
];
// Auto-discover all JSON files in the award-definitions directory
const files = readdirSync(AWARD_DEFINITIONS_DIR)
.filter(f => f.endsWith('.json'))
.sort();
for (const file of files) {
try {
@@ -45,6 +47,9 @@ function loadAwardDefinitions() {
logger.error('Error loading award definitions', { error: error.message });
}
// Cache the definitions for future calls
cachedAwardDefinitions = definitions;
return definitions;
}

View File

@@ -86,32 +86,6 @@ export function clearAllCache() {
return size;
}
/**
* Get cache statistics (for monitoring/debugging)
* @returns {object} Cache stats
*/
export function getCacheStats() {
const now = Date.now();
let expired = 0;
let valid = 0;
for (const [, value] of awardCache) {
const age = now - value.timestamp;
if (age > CACHE_TTL) {
expired++;
} else {
valid++;
}
}
return {
total: awardCache.size,
valid,
expired,
ttl: CACHE_TTL
};
}
/**
* Clean up expired cache entries (maintenance function)
* Can be called periodically to free memory

View File

@@ -4,6 +4,7 @@ import { max, sql, eq, and, desc } from 'drizzle-orm';
import { updateJobProgress } from './job-queue.service.js';
import { parseDCLResponse, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
import { invalidateUserCache, invalidateStatsCache } from './cache.service.js';
import { yieldToEventLoop, getQSOKey } from '../utils/sync-helpers.js';
/**
* DCL (DARC Community Logbook) Service
@@ -122,17 +123,6 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
}
}
/**
* Parse DCL API response from JSON
* Can be used for testing with example payloads
*
* @param {Object} jsonResponse - JSON response in DCL format
* @returns {Array} Array of parsed QSO records
*/
export function parseDCLJSONResponse(jsonResponse) {
return parseDCLResponse(jsonResponse);
}
/**
* Convert DCL ADIF QSO to database format
* @param {Object} adifQSO - Parsed ADIF QSO record
@@ -169,21 +159,6 @@ function convertQSODatabaseFormat(adifQSO, userId) {
};
}
/**
* Yield to event loop to allow other requests to be processed
* This prevents blocking the server during long-running sync operations
*/
function yieldToEventLoop() {
return new Promise(resolve => setImmediate(resolve));
}
/**
* Get QSO key for duplicate detection
*/
function getQSOKey(qso) {
return `${qso.callsign}|${qso.qsoDate}|${qso.timeOn}|${qso.band}|${qso.mode}`;
}
/**
* Sync QSOs from DCL to database (optimized with batch operations)
* Updates existing QSOs with DCL confirmation data

View File

@@ -4,7 +4,8 @@ import { max, sql, eq, and, or, desc, like } from 'drizzle-orm';
import { updateJobProgress } from './job-queue.service.js';
import { parseADIF, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
import { invalidateUserCache, getCachedStats, setCachedStats, invalidateStatsCache } from './cache.service.js';
import { trackQueryPerformance, getPerformanceSummary, resetPerformanceMetrics } from './performance.service.js';
import { trackQueryPerformance } from './performance.service.js';
import { yieldToEventLoop, getQSOKey } from '../utils/sync-helpers.js';
/**
* LoTW (Logbook of the World) Service
@@ -81,6 +82,7 @@ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
* Fetch QSOs from LoTW with retry support
*/
async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
const startTime = Date.now();
const url = 'https://lotw.arrl.org/lotwuser/lotwreport.adi';
const params = new URLSearchParams({
@@ -176,7 +178,7 @@ async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
}
}
const totalTime = Math.round((Date.now() - Date.now()) / 1000);
const totalTime = Math.round((Date.now() - startTime) / 1000);
return {
error: `LoTW sync failed: Report not ready after ${MAX_RETRIES} attempts (${totalTime}s). LoTW may be experiencing high load. Please try again later.`
};
@@ -210,21 +212,6 @@ function convertQSODatabaseFormat(adifQSO, userId) {
};
}
/**
* Yield to event loop to allow other requests to be processed
* This prevents blocking the server during long-running sync operations
*/
function yieldToEventLoop() {
return new Promise(resolve => setImmediate(resolve));
}
/**
* Get QSO key for duplicate detection
*/
function getQSOKey(qso) {
return `${qso.callsign}|${qso.qsoDate}|${qso.timeOn}|${qso.band}|${qso.mode}`;
}
/**
* Sync QSOs from LoTW to database (optimized with batch operations)
* @param {number} userId - User ID

View File

@@ -0,0 +1,23 @@
/**
* Sync Helper Utilities
*
* Shared utilities for LoTW and DCL sync operations
*/
/**
* Yield to event loop to allow other requests to be processed
* This prevents blocking the server during long-running sync operations
* @returns {Promise<void>}
*/
export function yieldToEventLoop() {
return new Promise(resolve => setImmediate(resolve));
}
/**
* Get QSO key for duplicate detection
* @param {object} qso - QSO object
* @returns {string} Unique key for the QSO
*/
export function getQSOKey(qso) {
return `${qso.callsign}|${qso.qsoDate}|${qso.timeOn}|${qso.band}|${qso.mode}`;
}