transmission-rss-manager/server-endpoints.js
MasterDraco d477d65ff5 Implement improved version handling and semantic versioning
- Add semver library for semantic version comparison
- Make package.json the single source of truth for version
- Add version check to only offer updates when remote version is higher
- Use APP_VERSION constant throughout server instead of hardcoded values
- Refactor update checking logic to use semver.gt() for proper comparison

This change improves robustness by ensuring updates are only
offered when a higher semantic version is available in the repository.

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-05 00:20:50 +00:00

167 lines
5.0 KiB
JavaScript

// Version and update endpoints for server.js
// Add these imports at the top of server.js
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const fs = require('fs');
const path = require('path');
const semver = require('semver'); // Add this - for semantic version comparison
// Add these endpoints
// Get system status including version and uptime
app.get('/api/system/status', async (req, res) => {
try {
// Get package.json for version info
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
const version = packageJson.version;
// Get system uptime
const uptimeSeconds = Math.floor(process.uptime());
const hours = Math.floor(uptimeSeconds / 3600);
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
const seconds = uptimeSeconds % 60;
const uptime = `${hours}h ${minutes}m ${seconds}s`;
// Check transmission connection
let transmissionStatus = 'Connected';
try {
await transmissionClient.sessionGet();
} catch (err) {
transmissionStatus = 'Disconnected';
}
res.json({
status: 'success',
data: {
version,
uptime,
transmissionStatus
}
});
} catch (error) {
console.error('Error getting system status:', error);
res.status(500).json({
status: 'error',
message: 'Failed to get system status'
});
}
});
// Check for updates
app.get('/api/system/check-updates', async (req, res) => {
try {
// Check if git is available and if this is a git repository
const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
if (!isGitRepo) {
return res.json({
status: 'error',
message: 'This installation is not set up for updates. Please use the bootstrap installer.'
});
}
// Get current version from the global APP_VERSION constant
const currentVersion = APP_VERSION;
// Check for test mode flag which forces update availability for testing
const testMode = req.query.test === 'true';
if (testMode) {
// In test mode, always return that an update is available
return res.json({
status: 'success',
data: {
updateAvailable: true,
currentVersion,
remoteVersion: '2.1.0-test',
commitsBehind: 1,
testMode: true
}
});
}
// Normal mode - fetch latest updates without applying them
await execAsync('git fetch');
// Check if we're behind the remote repository
const { stdout } = await execAsync('git rev-list HEAD..origin/main --count');
const behindCount = parseInt(stdout.trim());
if (behindCount > 0) {
// Get the new version from the remote package.json
const { stdout: remotePackageJson } = await execAsync('git show origin/main:package.json');
const remotePackage = JSON.parse(remotePackageJson);
const remoteVersion = remotePackage.version;
// Compare versions semantically - only consider it an update if remote version is higher
const isNewerVersion = semver.gt(remoteVersion, currentVersion);
return res.json({
status: 'success',
data: {
updateAvailable: isNewerVersion,
currentVersion,
remoteVersion,
commitsBehind: behindCount,
newerVersion: isNewerVersion
}
});
} else {
return res.json({
status: 'success',
data: {
updateAvailable: false,
currentVersion
}
});
}
} catch (error) {
console.error('Error checking for updates:', error);
res.status(500).json({
status: 'error',
message: 'Failed to check for updates'
});
}
});
// Apply updates
app.post('/api/system/update', async (req, res) => {
try {
// Check if git is available and if this is a git repository
const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
if (!isGitRepo) {
return res.status(400).json({
status: 'error',
message: 'This installation is not set up for updates. Please use the bootstrap installer.'
});
}
// Run the update script
const updateScriptPath = path.join(__dirname, 'scripts', 'update.sh');
// Make sure the update script is executable
await execAsync(`chmod +x ${updateScriptPath}`);
// Execute the update script
const { stdout, stderr } = await execAsync(updateScriptPath);
// If we get here, the update was successful
// The service will be restarted by the update script
res.json({
status: 'success',
message: 'Update applied successfully. The service will restart.',
data: {
output: stdout,
errors: stderr
}
});
} catch (error) {
console.error('Error applying update:', error);
res.status(500).json({
status: 'error',
message: 'Failed to apply update',
error: error.message
});
}
});