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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user