feat: add sync job cancel and rollback with real-time updates
Implement comprehensive sync job management with rollback capabilities and real-time status updates on the dashboard. ## Features ### Cancel & Rollback - Users can cancel failed or stale (>1h) sync jobs - Rollback deletes added QSOs and restores updated QSOs to previous state - Uses qso_changes table to track all modifications with before/after snapshots - Server-side validation prevents cancelling completed or active jobs ### Database Changes - Add qso_changes table to track QSO modifications per job - Stores change type (added/updated), before/after data snapshots - Enables precise rollback of sync operations - Migration script included ### Real-time Updates - Dashboard now polls for job updates every 2 seconds - Smart polling: starts when jobs active, stops when complete - Job status badges update in real-time (pending → running → completed) - Cancel button appears/disappears based on job state ### Backend - Fixed job ordering to show newest first (desc createdAt) - Track all QSO changes during LoTW/DCL sync operations - cancelJob() function handles rollback logic - DELETE /api/jobs/:jobId endpoint for cancelling jobs ### Frontend - jobsAPI.cancel() method for cancelling jobs - Dashboard shows last 5 sync jobs with status, stats, duration - Real-time job status updates via polling - Cancel button with confirmation dialog - Loading state and error handling ### Logging Fix - Changed from Bun.write() to fs.appendFile() for reliable log appending - Logs now persist across server restarts instead of being truncated Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
74
src/backend/migrations/add-qso-changes-table.js
Normal file
74
src/backend/migrations/add-qso-changes-table.js
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Migration: Add qso_changes table for sync job rollback
|
||||
*
|
||||
* This script adds the qso_changes table which tracks all QSO modifications
|
||||
* made by sync jobs, enabling rollback functionality for failed or stale jobs.
|
||||
*/
|
||||
|
||||
import Database from 'bun:sqlite';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// ES module equivalent of __dirname
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const dbPath = join(__dirname, '../award.db');
|
||||
const sqlite = new Database(dbPath);
|
||||
|
||||
async function migrate() {
|
||||
console.log('Starting migration: Add qso_changes table...');
|
||||
|
||||
try {
|
||||
// Check if table already exists
|
||||
const tableExists = sqlite.query(`
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='qso_changes'
|
||||
`).get();
|
||||
|
||||
if (tableExists) {
|
||||
console.log('Table qso_changes already exists. Migration complete.');
|
||||
sqlite.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create qso_changes table
|
||||
sqlite.exec(`
|
||||
CREATE TABLE qso_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
qso_id INTEGER,
|
||||
change_type TEXT NOT NULL,
|
||||
before_data TEXT,
|
||||
after_data TEXT,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
||||
FOREIGN KEY (job_id) REFERENCES sync_jobs(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (qso_id) REFERENCES qsos(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// Create index for faster lookups during rollback
|
||||
sqlite.exec(`
|
||||
CREATE INDEX idx_qso_changes_job_id ON qso_changes(job_id)
|
||||
`);
|
||||
|
||||
// Create index for change_type lookups
|
||||
sqlite.exec(`
|
||||
CREATE INDEX idx_qso_changes_change_type ON qso_changes(change_type)
|
||||
`);
|
||||
|
||||
console.log('Migration complete! Created qso_changes table with indexes.');
|
||||
} catch (error) {
|
||||
console.error('Migration failed:', error);
|
||||
sqlite.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
sqlite.close();
|
||||
}
|
||||
|
||||
// Run migration
|
||||
migrate().then(() => {
|
||||
console.log('Migration script completed successfully');
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user