- Fixed unused function parameters by prefixing with underscore - Removed unused imports and variables - Applied eslint --fix for automatic style fixes - Property shorthand - String template literals - Prefer const over let where appropriate - Spacing and formatting Reduces lint errors from 108+ to 78 (61 unused vars, 17 other issues) Related to CI lint failures in previous commit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
141 lines
4.9 KiB
JavaScript
141 lines
4.9 KiB
JavaScript
/**
|
|
* Sync Health Check Routes
|
|
* Monitors synchronization between file-based instructions and MongoDB
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const mongoose = require('mongoose');
|
|
const { authenticateToken, requireAdmin } = require('../middleware/auth.middleware');
|
|
const GovernanceRule = require('../models/GovernanceRule.model');
|
|
|
|
const INSTRUCTION_FILE = path.join(__dirname, '../../.claude/instruction-history.json');
|
|
|
|
/**
|
|
* GET /api/admin/sync/health
|
|
* Check synchronization health between file and database
|
|
*/
|
|
router.get('/health', authenticateToken, requireAdmin, async (req, res) => {
|
|
try {
|
|
// Check MongoDB connection
|
|
if (mongoose.connection.readyState !== 1) {
|
|
return res.json({
|
|
success: true,
|
|
health: {
|
|
status: 'warning',
|
|
message: 'Database not connected - sync health unavailable',
|
|
severity: 'warning',
|
|
timestamp: new Date().toISOString(),
|
|
counts: { file: 0, database: 0, difference: 0, differencePercent: 0 },
|
|
details: { missingInDatabase: [], orphanedInDatabase: [] },
|
|
recommendations: ['Start MongoDB server', 'Restart the application to auto-connect']
|
|
}
|
|
});
|
|
}
|
|
|
|
let fileInstructions = [];
|
|
let fileError = null;
|
|
|
|
if (fs.existsSync(INSTRUCTION_FILE)) {
|
|
try {
|
|
const fileData = JSON.parse(fs.readFileSync(INSTRUCTION_FILE, 'utf8'));
|
|
fileInstructions = (fileData.instructions || []).filter(i => i.active !== false);
|
|
} catch (err) {
|
|
fileError = err.message;
|
|
}
|
|
} else {
|
|
fileError = 'File not found';
|
|
}
|
|
|
|
const dbRules = await GovernanceRule.find({ active: true }).lean();
|
|
const fileCount = fileInstructions.length;
|
|
const dbCount = dbRules.length;
|
|
const difference = Math.abs(fileCount - dbCount);
|
|
const diffPercent = fileCount > 0 ? ((difference / fileCount) * 100).toFixed(1) : 0;
|
|
|
|
let status = 'healthy';
|
|
let message = 'File and database are synchronized';
|
|
let severity = 'success';
|
|
|
|
if (fileError) {
|
|
status = 'error';
|
|
message = `Cannot read instruction file: ${ fileError}`;
|
|
severity = 'error';
|
|
} else if (difference === 0) {
|
|
status = 'healthy';
|
|
message = 'Perfectly synchronized';
|
|
severity = 'success';
|
|
} else if (difference <= 2) {
|
|
status = 'warning';
|
|
message = `Minor desync: ${ difference } instruction${ difference !== 1 ? 's' : '' } differ`;
|
|
severity = 'warning';
|
|
} else if (difference <= 5) {
|
|
status = 'warning';
|
|
message = `Moderate desync: ${ difference } instructions differ (${ diffPercent }%)`;
|
|
severity = 'warning';
|
|
} else {
|
|
status = 'critical';
|
|
message = `Critical desync: ${ difference } instructions differ (${ diffPercent }%)`;
|
|
severity = 'error';
|
|
}
|
|
|
|
const fileIds = new Set(fileInstructions.map(i => i.id));
|
|
const dbIds = new Set(dbRules.map(r => r.id));
|
|
|
|
const missingInDb = fileInstructions
|
|
.filter(i => !dbIds.has(i.id))
|
|
.map(i => ({ id: i.id, text: `${i.text.substring(0, 60) }...` }));
|
|
|
|
const orphanedInDb = dbRules
|
|
.filter(r => !fileIds.has(r.id))
|
|
.map(r => ({ id: r.id, text: `${r.text.substring(0, 60) }...` }));
|
|
|
|
res.json({
|
|
success: true,
|
|
health: {
|
|
status,
|
|
message,
|
|
severity,
|
|
timestamp: new Date().toISOString(),
|
|
counts: { file: fileCount, database: dbCount, difference, differencePercent: parseFloat(diffPercent) },
|
|
details: { missingInDatabase: missingInDb, orphanedInDatabase: orphanedInDb },
|
|
recommendations: difference > 0 ? [
|
|
'Run: node scripts/sync-instructions-to-db.js --force',
|
|
'Or restart the server (auto-sync on startup)',
|
|
'Or wait for next session initialization'
|
|
] : []
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Sync health check error:', error);
|
|
res.status(500).json({ success: false, error: 'Failed to check sync health', message: error.message });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/admin/sync/trigger
|
|
* Manually trigger synchronization
|
|
*/
|
|
router.post('/trigger', authenticateToken, requireAdmin, async (req, res) => {
|
|
try {
|
|
const { syncInstructions } = require('../../scripts/sync-instructions-to-db.js');
|
|
const result = await syncInstructions({ silent: true });
|
|
|
|
if (result.success) {
|
|
res.json({
|
|
success: true,
|
|
message: 'Synchronization completed successfully',
|
|
result: { added: result.added, updated: result.updated, deactivated: result.deactivated, finalCount: result.finalCount }
|
|
});
|
|
} else {
|
|
res.status(500).json({ success: false, error: 'Synchronization failed', message: result.error || 'Unknown error' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Manual sync trigger error:', error);
|
|
res.status(500).json({ success: false, error: 'Failed to trigger synchronization', message: error.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|