const express = require('express'); const { CONFIG } = require('../config'); const { applyConfigUpdates, readAllConfig } = require('../services/configPersistence'); const { logSystem } = require('../services/debugLog'); const router = express.Router(); // Middleware: verify internal secret router.use((req, res, next) => { const secret = req.headers['x-internal-secret']; if (!CONFIG.INTERNAL_API_SECRET || secret !== CONFIG.INTERNAL_API_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); // GET /config — return all current .env values (redacted secrets) router.get('/config', (req, res) => { const map = readAllConfig(); const obj = {}; const REDACTED = ['DISCORD_TOKEN', 'REFRESH_TOKEN', 'GOOGLE_CLIENT_SECRET', 'MONGODB_URI', 'INTERNAL_API_SECRET', 'SETTINGS_ADMIN_PASSWORD']; for (const [k, v] of map) { obj[k] = REDACTED.includes(k) ? '••••••••' : v; } res.json(obj); }); // POST /config — apply config updates router.post('/config', express.json(), async (req, res) => { const updates = req.body; if (!updates || typeof updates !== 'object') { return res.status(400).json({ error: 'Invalid body' }); } const result = applyConfigUpdates(updates); await logSystem('Config updated via settings UI', [ { name: 'Keys updated', value: result.applied.join(', ') || 'none', inline: false }, { name: 'Errors', value: result.errors.join(', ') || 'none', inline: false } ]).catch(() => {}); res.json(result); }); // GET /discord/guild — return guild info for smart dropdowns router.get('/discord/guild', async (req, res) => { try { const client = require('../api/bosscordClient').getBot(); if (!client) return res.status(503).json({ error: 'Bot not ready' }); const guild = client.guilds.cache.get(CONFIG.DISCORD_GUILD_ID); if (!guild) return res.status(404).json({ error: 'Guild not found' }); await guild.members.fetch().catch(() => {}); const channels = guild.channels.cache .filter(c => [0, 4, 5, 15].includes(c.type)) .map(c => ({ id: c.id, name: c.name, type: c.type, parentId: c.parentId })) .sort((a, b) => a.name.localeCompare(b.name)); const roles = guild.roles.cache .filter(r => !r.managed && r.id !== guild.id) .map(r => ({ id: r.id, name: r.name, color: r.hexColor })) .sort((a, b) => b.position - a.position); const members = guild.members.cache .filter(m => !m.user.bot) .map(m => ({ id: m.id, username: m.user.username, displayName: m.displayName, avatar: m.user.displayAvatarURL({ size: 32 }) })) .sort((a, b) => a.displayName.localeCompare(b.displayName)); const categories = guild.channels.cache .filter(c => c.type === 4) .map(c => ({ id: c.id, name: c.name })) .sort((a, b) => a.name.localeCompare(b.name)); res.json({ channels, roles, members, categories }); } catch (err) { res.status(500).json({ error: err.message }); } }); // POST /restart — restart the bot process let scheduledRestart = null; router.post('/restart', express.json(), (req, res) => { const { mode, scheduledFor } = req.body; if (mode === 'immediate') { res.json({ ok: true, mode }); setTimeout(() => { console.log('[restart] Restarting bot process...'); process.exit(0); // Docker/systemd will restart }, 1500); return; } if (mode === 'scheduled' && scheduledFor) { const delay = new Date(scheduledFor).getTime() - Date.now(); if (delay <= 0) return res.status(400).json({ error: 'Scheduled time is in the past' }); if (scheduledRestart) clearTimeout(scheduledRestart); scheduledRestart = setTimeout(() => { console.log('[restart] Scheduled restart firing...'); process.exit(0); }, delay); res.json({ ok: true, mode, scheduledFor, delayMs: delay }); return; } if (mode === 'cancel_scheduled') { if (scheduledRestart) { clearTimeout(scheduledRestart); scheduledRestart = null; } res.json({ ok: true, cancelled: true }); return; } if (mode === 'pending') { res.json({ ok: true, mode: 'pending', note: 'Restart required on next manual restart' }); return; } res.status(400).json({ error: 'Invalid mode' }); }); router.get('/restart/status', (req, res) => { res.json({ scheduledRestart: !!scheduledRestart }); }); module.exports = router;