fix: preserve DOK data when DCL doesn't send values
When DCL sync updates a QSO without DOK fields, the previous code would write empty strings to the database, overwriting any existing DOK data that was previously imported or manually entered. Now only updates DOK/grid fields when DCL actually provides non-empty values, preserving existing data from other sources. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,18 @@ import { parseDCLResponse, normalizeBand, normalizeMode } from '../utils/adif-pa
|
||||
*
|
||||
* DCL Information:
|
||||
* - Website: https://dcl.darc.de/
|
||||
* - API: Coming soon (currently in development)
|
||||
* - ADIF Export: https://dcl.darc.de/dml/export_adif_form.php (manual only)
|
||||
* - API Endpoint: https://dings.dcl.darc.de/api/adiexport
|
||||
* - DOK fields: MY_DARC_DOK (user's DOK), DARC_DOK (partner's DOK)
|
||||
*
|
||||
* API Request Format (POST):
|
||||
* {
|
||||
* "key": "API_KEY",
|
||||
* "limit": null,
|
||||
* "qsl_since": null,
|
||||
* "qso_since": null,
|
||||
* "cnf_only": null
|
||||
* }
|
||||
*
|
||||
* Expected API Response Format:
|
||||
* {
|
||||
* "adif": "<ADIF_VER:5>3.1.3\\n<CREATED_TIMESTAMP:15>20260117 095453\\n<EOH>\\n..."
|
||||
@@ -20,13 +28,11 @@ import { parseDCLResponse, normalizeBand, normalizeMode } from '../utils/adif-pa
|
||||
*/
|
||||
|
||||
const REQUEST_TIMEOUT = 60000;
|
||||
const DCL_API_URL = 'https://dings.dcl.darc.de/api/adiexport';
|
||||
|
||||
/**
|
||||
* Fetch QSOs from DCL API
|
||||
*
|
||||
* When DCL provides their API, update the URL and parameters.
|
||||
* Expected response format: { "adif": "<ADIF data>" }
|
||||
*
|
||||
* @param {string} dclApiKey - DCL API key
|
||||
* @param {Date|null} sinceDate - Last sync date for incremental sync
|
||||
* @returns {Promise<Array>} Array of parsed QSO records
|
||||
@@ -37,30 +43,33 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
|
||||
sinceDate: sinceDate?.toISOString(),
|
||||
});
|
||||
|
||||
// TODO: Update URL when DCL publishes their API endpoint
|
||||
const url = 'https://dcl.darc.de/api/export'; // Placeholder URL
|
||||
|
||||
const params = new URLSearchParams({
|
||||
api_key: dclApiKey,
|
||||
format: 'json',
|
||||
qsl: 'yes',
|
||||
});
|
||||
// Build request body
|
||||
const requestBody = {
|
||||
key: dclApiKey,
|
||||
limit: 50000,
|
||||
qsl_since: null,
|
||||
qso_since: null,
|
||||
cnf_only: null,
|
||||
};
|
||||
|
||||
// Add date filter for incremental sync if provided
|
||||
if (sinceDate) {
|
||||
const dateStr = sinceDate.toISOString().split('T')[0].replace(/-/g, '');
|
||||
params.append('qsl_since', dateStr);
|
||||
requestBody.qsl_since = dateStr;
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
|
||||
|
||||
const response = await fetch(`${url}?${params}`, {
|
||||
const response = await fetch(DCL_API_URL, {
|
||||
method: 'POST',
|
||||
signal: controller.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
@@ -69,9 +78,10 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('Invalid DCL API key. Please check your DCL credentials in Settings.');
|
||||
} else if (response.status === 404) {
|
||||
throw new Error('DCL API endpoint not found. The DCL API may not be available yet.');
|
||||
throw new Error('DCL API endpoint not found.');
|
||||
} else {
|
||||
throw new Error(`DCL API error: ${response.status} ${response.statusText}`);
|
||||
const errorText = await response.text();
|
||||
throw new Error(`DCL API error: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +92,7 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
|
||||
|
||||
logger.info('Successfully fetched QSOs from DCL', {
|
||||
total: qsos.length,
|
||||
hasConfirmations: qsos.filter(q => qso.dcl_qsl_rcvd === 'Y').length,
|
||||
hasConfirmations: qsos.filter(q => q.dcl_qsl_rcvd === 'Y').length,
|
||||
});
|
||||
|
||||
return qsos;
|
||||
@@ -94,7 +104,6 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
|
||||
|
||||
logger.error('Failed to fetch from DCL', {
|
||||
error: error.message,
|
||||
url: url.replace(/api_key=[^&]+/, 'api_key=***'),
|
||||
});
|
||||
|
||||
throw error;
|
||||
@@ -103,7 +112,7 @@ export async function fetchQSOsFromDCL(dclApiKey, sinceDate = null) {
|
||||
|
||||
/**
|
||||
* Parse DCL API response from JSON
|
||||
* This function exists for testing with example payloads before DCL API is available
|
||||
* Can be used for testing with example payloads
|
||||
*
|
||||
* @param {Object} jsonResponse - JSON response in DCL format
|
||||
* @returns {Array} Array of parsed QSO records
|
||||
@@ -232,16 +241,25 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
|
||||
|
||||
if (dataChanged) {
|
||||
// Update existing QSO with changed DCL confirmation and DOK data
|
||||
await db
|
||||
.update(qsos)
|
||||
.set({
|
||||
// Only update DOK/grid fields if DCL actually sent values (non-empty)
|
||||
const updateData = {
|
||||
dclQslRdate: dbQSO.dclQslRdate,
|
||||
dclQslRstatus: dbQSO.dclQslRstatus,
|
||||
darcDok: dbQSO.darcDok || existingQSO.darcDok,
|
||||
myDarcDok: dbQSO.myDarcDok || existingQSO.myDarcDok,
|
||||
grid: dbQSO.grid || existingQSO.grid,
|
||||
gridSource: dbQSO.gridSource || existingQSO.gridSource,
|
||||
})
|
||||
};
|
||||
|
||||
// Only add DOK fields if DCL sent them
|
||||
if (dbQSO.darcDok) updateData.darcDok = dbQSO.darcDok;
|
||||
if (dbQSO.myDarcDok) updateData.myDarcDok = dbQSO.myDarcDok;
|
||||
|
||||
// Only update grid if DCL sent one
|
||||
if (dbQSO.grid) {
|
||||
updateData.grid = dbQSO.grid;
|
||||
updateData.gridSource = dbQSO.gridSource;
|
||||
}
|
||||
|
||||
await db
|
||||
.update(qsos)
|
||||
.set(updateData)
|
||||
.where(eq(qsos.id, existingQSO.id));
|
||||
updatedCount++;
|
||||
// Track updated QSO (CALL and DATE)
|
||||
@@ -325,8 +343,6 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
|
||||
/**
|
||||
* Get last DCL QSL date for incremental sync
|
||||
*
|
||||
* TODO: Implement when DCL provides API
|
||||
*
|
||||
* @param {number} userId - User ID
|
||||
* @returns {Promise<Date|null>} Last QSL date or null
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user