const express = require('express'); const fs = require('fs').promises; const path = require('path'); const router = express.Router(); const SystemSettings = require('../models/SystemSettings'); const config = require('../config'); module.exports = (pool, query, authMiddleware) => { // Apply authentication middleware to all routes router.use(authMiddleware); /** * Get all settings */ router.get('/', async (req, res, next) => { try { // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } const settings = await SystemSettings.getAllSettings(pool, query); // Group settings by category const groupedSettings = settings.reduce((acc, setting) => { if (!acc[setting.category]) { acc[setting.category] = []; } acc[setting.category].push(setting); return acc; }, {}); res.json(groupedSettings); } catch (error) { next(error); } }); /** * Get settings by category */ router.get('/category/:category', async (req, res, next) => { try { const { category } = req.params; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } const settings = await SystemSettings.getSettingsByCategory(pool, query, category); res.json(settings); } catch (error) { next(error); } }); /** * Get a specific setting */ router.get('/:key', async (req, res, next) => { try { const { key } = req.params; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } const setting = await SystemSettings.getSetting(pool, query, key); if (!setting) { return res.status(404).json({ error: true, message: `Setting with key "${key}" not found` }); } res.json(setting); } catch (error) { next(error); } }); /** * Update a single setting */ router.put('/:key', async (req, res, next) => { try { const { key } = req.params; const { value, category } = req.body; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } if (value === undefined || !category) { return res.status(400).json({ error: true, message: 'Value and category are required' }); } const updatedSetting = await SystemSettings.updateSetting(pool, query, key, value, category); // Update config in memory updateConfigInMemory(updatedSetting); // Update environment file await updateEnvironmentFile(); res.json({ message: 'Setting updated successfully', setting: updatedSetting }); } catch (error) { next(error); } }); /** * Update multiple settings at once */ router.post('/batch', async (req, res, next) => { try { const { settings } = req.body; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } if (!Array.isArray(settings) || settings.length === 0) { return res.status(400).json({ error: true, message: 'Settings array is required' }); } // Validate all settings have required fields for (const setting of settings) { if (!setting.key || setting.value === undefined || !setting.category) { return res.status(400).json({ error: true, message: 'Each setting must have key, value, and category fields' }); } } const updatedSettings = await SystemSettings.updateSettings(pool, query, settings); // Update config in memory for each setting updatedSettings.forEach(setting => { updateConfigInMemory(setting); }); // Update environment file await updateEnvironmentFile(); res.json({ message: 'Settings updated successfully', settings: updatedSettings }); } catch (error) { next(error); } }); /** * Delete a setting */ router.delete('/:key', async (req, res, next) => { try { const { key } = req.params; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } await SystemSettings.deleteSetting(pool, query, key); // Update environment file await updateEnvironmentFile(); res.json({ message: `Setting "${key}" deleted successfully` }); } catch (error) { next(error); } }); /** * Helper function to update config in memory */ function updateConfigInMemory(setting) { const { key, value, category } = setting; // Map database settings to config structure if (category === 'email') { if (key === 'smtp_host') config.email.host = value; if (key === 'smtp_port') config.email.port = parseInt(value, 10); if (key === 'smtp_user') config.email.user = value; if (key === 'smtp_password') config.email.pass = value; if (key === 'smtp_from_email') config.email.reply = value; } else if (category === 'site') { if (key === 'site_name') config.site.name = value; if (key === 'site_domain') config.site.domain = value; if (key === 'site_api_domain') config.site.apiDomain = value; if (key === 'site_protocol') config.site.protocol = value; if (key === 'site_environment') config.environment = value; } // You can add more mappings for other categories here } /** * Helper function to update environment file */ async function updateEnvironmentFile() { try { // Get all settings from database const allSettings = await SystemSettings.getAllSettings(pool, query); config.updateFromDatabase(allSettings) // Build environment variables string let envContent = ''; // Add standard environment variables envContent += `PORT=${process.env.PORT || config.port || 4000}\n`; envContent += `NODE_ENV=${process.env.NODE_ENV || 'development'}\n\n`; // Add database configuration - use existing config values as fallbacks envContent += `# Database connection\n`; envContent += `DB_HOST=${config.db.host}\n`; envContent += `DB_USER=${config.db.user}\n`; envContent += `DB_PASSWORD=${config.db.password}\n`; envContent += `DB_NAME=${config.db.database}\n`; envContent += `DB_PORT=${config.db.port}\n`; envContent += `POSTGRES_USER=${config.db.user}\n`; envContent += `POSTGRES_PASSWORD=${config.db.password}\n`; envContent += `POSTGRES_DB=${config.db.database}\n`; // Get site environment from settings or use existing config const siteEnvSetting = allSettings.find(s => s.key === 'site_environment'); const currentEnvironment = siteEnvSetting?.value || config.environment || process.env.ENVIRONMENT || 'beta'; envContent += `ENVIRONMENT=${currentEnvironment}\n\n`; // Add email configuration with fallbacks to existing config envContent += `# Email configuration\n`; const emailSettings = allSettings.filter(s => s.category === 'email'); const smtpHost = emailSettings.find(s => s.key === 'smtp_host'); const smtpPort = emailSettings.find(s => s.key === 'smtp_port'); const smtpUser = emailSettings.find(s => s.key === 'smtp_user'); const smtpPass = emailSettings.find(s => s.key === 'smtp_password'); const smtpReply = emailSettings.find(s => s.key === 'smtp_from_email'); // Use existing config values as fallbacks in this order: database setting → config value → env value → default envContent += `EMAIL_HOST=${smtpHost?.value || config.email.host || process.env.EMAIL_HOST || 'smtp.postmarkapp.com'}\n`; envContent += `EMAIL_PORT=${smtpPort?.value || config.email.port || process.env.EMAIL_PORT || '587'}\n`; envContent += `EMAIL_USER=${smtpUser?.value || config.email.user || process.env.EMAIL_USER || ''}\n`; envContent += `EMAIL_PASS=${smtpPass?.value || config.email.pass || process.env.EMAIL_PASS || ''}\n`; envContent += `EMAIL_REPLY=${smtpReply?.value || config.email.reply || process.env.EMAIL_REPLY || 'noreply@2many.ca'}\n\n`; // Add payment configuration with fallbacks to existing values const paymentSettings = allSettings.filter(s => s.category === 'payment'); if (paymentSettings.length > 0 || process.env.STRIPE_PUBLIC_KEY || process.env.STRIPE_SECRET_KEY) { envContent += `# Payment configuration\n`; const stripePublic = paymentSettings.find(s => s.key === 'stripe_public_key'); const stripeSecret = paymentSettings.find(s => s.key === 'stripe_secret_key'); // Include payment settings if they exist in either DB or environment if (stripePublic?.value || process.env.STRIPE_PUBLIC_KEY) { envContent += `STRIPE_PUBLIC_KEY=${stripePublic?.value || process.env.STRIPE_PUBLIC_KEY}\n`; } if (stripeSecret?.value || process.env.STRIPE_SECRET_KEY) { envContent += `STRIPE_SECRET_KEY=${stripeSecret?.value || process.env.STRIPE_SECRET_KEY}\n`; } } // Write to .env file const envPath = path.join(__dirname, '../../.env'); await fs.writeFile(envPath, envContent); console.log('Environment file updated successfully'); return true; } catch (error) { console.error('Error updating environment file:', error); throw error; } } return router; };