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 { db, logger } from '../config.js';
|
||||||
import { qsos } from '../db/schema/index.js';
|
import { qsos } from '../db/schema/index.js';
|
||||||
import { eq, and, or, desc, sql } from 'drizzle-orm';
|
import { eq, and, or, desc, sql } from 'drizzle-orm';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync, readdirSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { getCachedAwardProgress, setCachedAwardProgress } from './cache.service.js';
|
import { getCachedAwardProgress, setCachedAwardProgress } from './cache.service.js';
|
||||||
|
|
||||||
@@ -13,23 +13,25 @@ import { getCachedAwardProgress, setCachedAwardProgress } from './cache.service.
|
|||||||
// Load award definitions from files
|
// Load award definitions from files
|
||||||
const AWARD_DEFINITIONS_DIR = join(process.cwd(), 'award-definitions');
|
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() {
|
function loadAwardDefinitions() {
|
||||||
|
// Return cached definitions if available
|
||||||
|
if (cachedAwardDefinitions) {
|
||||||
|
return cachedAwardDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
const definitions = [];
|
const definitions = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = [
|
// Auto-discover all JSON files in the award-definitions directory
|
||||||
'dxcc.json',
|
const files = readdirSync(AWARD_DEFINITIONS_DIR)
|
||||||
'dxcc-sat.json',
|
.filter(f => f.endsWith('.json'))
|
||||||
'was.json',
|
.sort();
|
||||||
'vucc-sat.json',
|
|
||||||
'sat-rs44.json',
|
|
||||||
'special-stations.json',
|
|
||||||
'dld.json',
|
|
||||||
'73-on-73.json',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
@@ -45,6 +47,9 @@ function loadAwardDefinitions() {
|
|||||||
logger.error('Error loading award definitions', { error: error.message });
|
logger.error('Error loading award definitions', { error: error.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the definitions for future calls
|
||||||
|
cachedAwardDefinitions = definitions;
|
||||||
|
|
||||||
return definitions;
|
return definitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,32 +86,6 @@ export function clearAllCache() {
|
|||||||
return size;
|
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)
|
* Clean up expired cache entries (maintenance function)
|
||||||
* Can be called periodically to free memory
|
* 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 { updateJobProgress } from './job-queue.service.js';
|
||||||
import { parseDCLResponse, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
|
import { parseDCLResponse, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
|
||||||
import { invalidateUserCache, invalidateStatsCache } from './cache.service.js';
|
import { invalidateUserCache, invalidateStatsCache } from './cache.service.js';
|
||||||
|
import { yieldToEventLoop, getQSOKey } from '../utils/sync-helpers.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DCL (DARC Community Logbook) Service
|
* 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
|
* Convert DCL ADIF QSO to database format
|
||||||
* @param {Object} adifQSO - Parsed ADIF QSO record
|
* @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)
|
* Sync QSOs from DCL to database (optimized with batch operations)
|
||||||
* Updates existing QSOs with DCL confirmation data
|
* 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 { updateJobProgress } from './job-queue.service.js';
|
||||||
import { parseADIF, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
|
import { parseADIF, normalizeBand, normalizeMode } from '../utils/adif-parser.js';
|
||||||
import { invalidateUserCache, getCachedStats, setCachedStats, invalidateStatsCache } from './cache.service.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
|
* 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
|
* Fetch QSOs from LoTW with retry support
|
||||||
*/
|
*/
|
||||||
async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
|
async function fetchQSOsFromLoTW(lotwUsername, lotwPassword, sinceDate = null) {
|
||||||
|
const startTime = Date.now();
|
||||||
const url = 'https://lotw.arrl.org/lotwuser/lotwreport.adi';
|
const url = 'https://lotw.arrl.org/lotwuser/lotwreport.adi';
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
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 {
|
return {
|
||||||
error: `LoTW sync failed: Report not ready after ${MAX_RETRIES} attempts (${totalTime}s). LoTW may be experiencing high load. Please try again later.`
|
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)
|
* Sync QSOs from LoTW to database (optimized with batch operations)
|
||||||
* @param {number} userId - User ID
|
* @param {number} userId - User ID
|
||||||
|
|||||||
23
src/backend/utils/sync-helpers.js
Normal file
23
src/backend/utils/sync-helpers.js
Normal 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}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user