Files
apxtri/models/Towns.js
T
2026-09-26 21:42:06 +00:00

137 lines
5.3 KiB
JavaScript
Executable File

const fs = require("fs-extra");
const path = require("path");
const Odmdb = require("./Odmdb.js");
const DATA_PATH = process.env.DATAPATH;
const TRIBE_NAME = process.env.TOWN;
const log = process.env.ACTIVELOG?.split(',').includes('Towns');
/**
* Manages towns, which represent server instances in the network.
*/
class Towns {
/**
* Changes the owner (mayor) of a town.
* @param {string} newOwnerAlias - The alias of the new owner.
* @param {string} requestedByAlias - The alias of the user requesting the change (must be the current owner).
* @returns {Promise<object>} A response object.
*/
static async changeowner(newOwnerAlias, requestedByAlias) {
try {
const townsDb = new Odmdb('towns', TRIBE_NAME);
const pagansDb = new Odmdb('pagans', TRIBE_NAME);
const confPath = path.join(DATA_PATH, TRIBE_NAME, 'conf.json');
// 1. Verify the new owner exists as a pagan
const newOwnerResult = await pagansDb.read(newOwnerAlias, { xprofils: ['admin'] });
if (newOwnerResult.status !== 200) {
return { status: 404, ref: "Towns", msg: "new_owner_alias_not_found", data: { alias: newOwnerAlias } };
}
// 2. Verify the requester is the current mayor
const townConf = await fs.readJson(confPath);
if (townConf.mayorId !== requestedByAlias) {
return { status: 403, ref: "Towns", msg: "permission_denied_not_current_mayor" };
}
// 3. Update the town's data file
const townId = townConf.townId;
const townUpdateData = { townId, mayorId: newOwnerAlias };
const role = { xprofils: ['admin', 'mayor'], xalias: requestedByAlias };
const updateResult = await townsDb.update( townUpdateData, role);
if (updateResult.status !== 200) {
return updateResult; // Forward the error from Odmdb
}
// 4. Update the local server configuration file
townConf.mayorId = newOwnerAlias;
await fs.outputJson(confPath, townConf, { spaces: 2 });
if (log) console.log(`[Towns] Town owner changed successfully to '${newOwnerAlias}' by '${requestedByAlias}'.`);
return { status: 200, ref: "Towns", msg: "owner_change_successful", data: { alias: newOwnerAlias, townId } };
} catch (error) {
console.error(`[Towns] Error changing owner to '${newOwnerAlias}':`, error);
return { status: 500, ref: "Towns", msg: "internal_server_error", error: error.message };
}
}
/**
* @deprecated The create method contains server setup logic (like nginx config)
* and should not be part of a data model. This logic should be moved to a
* dedicated setup or installation script.
*/
static create() {
console.error("[Towns] The 'create' method is deprecated and should not be used. Please use a dedicated setup script.");
return { status: 501, ref: "Towns", msg: "create_method_is_deprecated" };
}
static async addCrontabEntry(cronExpression, command, identifier) {
try {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const tmpCronFile = path.join(DATA_PATH, TRIBE_NAME, 'tmp', `cron_${Date.now()}.txt`);
await fs.ensureDir(path.dirname(tmpCronFile));
const { stdout: currentCron } = await execAsync('crontab -l 2>/dev/null || true');
const cronLines = currentCron.split('\n').filter(line => {
if (!line.trim()) return false;
if (identifier && line.includes(identifier)) return false;
return true;
});
const cronEntry = `${cronExpression} ${command} # ${identifier}`;
cronLines.push(cronEntry);
await fs.writeFile(tmpCronFile, cronLines.join('\n') + '\n');
await execAsync(`crontab "${tmpCronFile}"`);
await fs.remove(tmpCronFile);
if (log) console.log(`[Towns] Crontab entry added: ${cronEntry}`);
return { status: 200, ref: "Towns", msg: "crontab_entry_added", data: { cronEntry } };
} catch (error) {
console.error('[Towns] Error adding crontab entry:', error);
return { status: 500, ref: "Towns", msg: "crontab_entry_failed", error: error.message };
}
}
static async removeCrontabEntry(identifier) {
try {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const tmpCronFile = path.join(DATA_PATH, TRIBE_NAME, 'tmp', `cron_${Date.now()}.txt`);
await fs.ensureDir(path.dirname(tmpCronFile));
const { stdout: currentCron } = await execAsync('crontab -l 2>/dev/null || true');
const cronLines = currentCron.split('\n').filter(line => {
if (!line.trim()) return false;
if (identifier && line.includes(identifier)) return false;
return true;
});
await fs.writeFile(tmpCronFile, cronLines.join('\n') + '\n');
await execAsync(`crontab "${tmpCronFile}"`);
await fs.remove(tmpCronFile);
if (log) console.log(`[Towns] Crontab entries removed for: ${identifier}`);
return { status: 200, ref: "Towns", msg: "crontab_entry_removed", data: { identifier } };
} catch (error) {
console.error('[Towns] Error removing crontab entry:', error);
return { status: 500, ref: "Towns", msg: "crontab_remove_failed", error: error.message };
}
}
}
module.exports = Towns;