158 lines
8.3 KiB
JavaScript
158 lines
8.3 KiB
JavaScript
const express = require('express');
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const sharp = require('sharp');
|
|
const router = express.Router();
|
|
const { query, queryOne, exec } = require('../models/db');
|
|
const { authMiddleware, adminMiddleware } = require('../middleware/auth');
|
|
|
|
function makeIconStorage(prefix) {
|
|
return multer.diskStorage({
|
|
destination: '/app/uploads/logos',
|
|
filename: (req, file, cb) => cb(null, `${prefix}_${Date.now()}${path.extname(file.originalname)}`),
|
|
});
|
|
}
|
|
const iconOpts = {
|
|
limits: { fileSize: 1 * 1024 * 1024 },
|
|
fileFilter: (req, file, cb) => file.mimetype.startsWith('image/') ? cb(null, true) : cb(new Error('Images only')),
|
|
};
|
|
const uploadLogo = multer({ storage: makeIconStorage('logo'), ...iconOpts });
|
|
const uploadNewChat = multer({ storage: makeIconStorage('newchat'), ...iconOpts });
|
|
const uploadGroupInfo = multer({ storage: makeIconStorage('groupinfo'), ...iconOpts });
|
|
|
|
// Helper: upsert a setting
|
|
async function setSetting(schema, key, value) {
|
|
await exec(schema,
|
|
"INSERT INTO settings (key,value) VALUES ($1,$2) ON CONFLICT(key) DO UPDATE SET value=$2, updated_at=NOW()",
|
|
[key, value]
|
|
);
|
|
}
|
|
|
|
// GET /api/settings
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const rows = await query(req.schema, 'SELECT key, value FROM settings');
|
|
const obj = {};
|
|
for (const r of rows) obj[r.key] = r.value;
|
|
const admin = await queryOne(req.schema, 'SELECT email FROM users WHERE is_default_admin = TRUE');
|
|
if (admin) obj.admin_email = admin.email;
|
|
obj.app_version = process.env.ROSTERCHIRP_VERSION || 'dev';
|
|
obj.user_pass = process.env.USER_PASS || 'user@1234';
|
|
// Tell the frontend whether this request came from the HOST_DOMAIN.
|
|
// Used to show/hide the Control Panel menu item — only visible on the host's own domain.
|
|
const reqHost = (req.headers.host || '').toLowerCase().split(':')[0];
|
|
const hostDomain = (process.env.HOST_DOMAIN || '').toLowerCase();
|
|
obj.is_host_domain = (
|
|
process.env.APP_TYPE === 'host' &&
|
|
!!hostDomain &&
|
|
(reqHost === hostDomain || reqHost === `www.${hostDomain}` || reqHost === 'localhost')
|
|
) ? 'true' : 'false';
|
|
res.json({ settings: obj });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.patch('/app-name', authMiddleware, adminMiddleware, async (req, res) => {
|
|
const { name } = req.body;
|
|
if (!name?.trim()) return res.status(400).json({ error: 'Name required' });
|
|
try {
|
|
await exec(req.schema, "UPDATE settings SET value=$1, updated_at=NOW() WHERE key='app_name'", [name.trim()]);
|
|
res.json({ success: true, name: name.trim() });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.post('/logo', authMiddleware, adminMiddleware, uploadLogo.single('logo'), async (req, res) => {
|
|
if (!req.file) return res.status(400).json({ error: 'No file' });
|
|
const logoUrl = `/uploads/logos/${req.file.filename}`;
|
|
try {
|
|
await sharp(req.file.path).resize(192,192,{fit:'contain',background:{r:255,g:255,b:255,alpha:0}}).png().toFile('/app/uploads/logos/pwa-icon-192.png');
|
|
await sharp(req.file.path).resize(512,512,{fit:'contain',background:{r:255,g:255,b:255,alpha:0}}).png().toFile('/app/uploads/logos/pwa-icon-512.png');
|
|
await exec(req.schema, "UPDATE settings SET value=$1, updated_at=NOW() WHERE key='logo_url'", [logoUrl]);
|
|
await setSetting(req.schema, 'pwa_icon_192', '/uploads/logos/pwa-icon-192.png');
|
|
await setSetting(req.schema, 'pwa_icon_512', '/uploads/logos/pwa-icon-512.png');
|
|
res.json({ logoUrl });
|
|
} catch (err) {
|
|
console.error('[Logo] icon gen failed:', err.message);
|
|
await exec(req.schema, "UPDATE settings SET value=$1, updated_at=NOW() WHERE key='logo_url'", [logoUrl]);
|
|
res.json({ logoUrl });
|
|
}
|
|
});
|
|
|
|
router.post('/icon-newchat', authMiddleware, adminMiddleware, uploadNewChat.single('icon'), async (req, res) => {
|
|
if (!req.file) return res.status(400).json({ error: 'No file' });
|
|
const iconUrl = `/uploads/logos/${req.file.filename}`;
|
|
try { await setSetting(req.schema, 'icon_newchat', iconUrl); res.json({ iconUrl }); }
|
|
catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.post('/icon-groupinfo', authMiddleware, adminMiddleware, uploadGroupInfo.single('icon'), async (req, res) => {
|
|
if (!req.file) return res.status(400).json({ error: 'No file' });
|
|
const iconUrl = `/uploads/logos/${req.file.filename}`;
|
|
try { await setSetting(req.schema, 'icon_groupinfo', iconUrl); res.json({ iconUrl }); }
|
|
catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.patch('/colors', authMiddleware, adminMiddleware, async (req, res) => {
|
|
const { colorTitle, colorTitleDark, colorAvatarPublic, colorAvatarDm } = req.body;
|
|
try {
|
|
if (colorTitle !== undefined) await setSetting(req.schema, 'color_title', colorTitle || '');
|
|
if (colorTitleDark !== undefined) await setSetting(req.schema, 'color_title_dark', colorTitleDark || '');
|
|
if (colorAvatarPublic !== undefined) await setSetting(req.schema, 'color_avatar_public', colorAvatarPublic || '');
|
|
if (colorAvatarDm !== undefined) await setSetting(req.schema, 'color_avatar_dm', colorAvatarDm || '');
|
|
res.json({ success: true });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.post('/reset', authMiddleware, adminMiddleware, async (req, res) => {
|
|
try {
|
|
const originalName = process.env.APP_NAME || 'rosterchirp';
|
|
await exec(req.schema, "UPDATE settings SET value=$1, updated_at=NOW() WHERE key='app_name'", [originalName]);
|
|
await exec(req.schema, "UPDATE settings SET value='', updated_at=NOW() WHERE key='logo_url'");
|
|
await exec(req.schema, "UPDATE settings SET value='', updated_at=NOW() WHERE key IN ('icon_newchat','icon_groupinfo','pwa_icon_192','pwa_icon_512','color_title','color_title_dark','color_avatar_public','color_avatar_dm')");
|
|
res.json({ success: true });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
const VALID_CODES = {
|
|
'ROSTERCHIRP-TEAM-2024': { appType:'RosterChirp-Team', branding:true, groupManager:true, scheduleManager:true },
|
|
'ROSTERCHIRP-BRAND-2024': { appType:'RosterChirp-Brand', branding:true, groupManager:false, scheduleManager:false },
|
|
'ROSTERCHIRP-FULL-2024': { appType:'RosterChirp-Team', branding:true, groupManager:true, scheduleManager:true },
|
|
};
|
|
|
|
router.post('/register', authMiddleware, adminMiddleware, async (req, res) => {
|
|
const { code } = req.body;
|
|
try {
|
|
if (!code?.trim()) {
|
|
await setSetting(req.schema, 'registration_code', '');
|
|
await setSetting(req.schema, 'app_type', 'RosterChirp-Chat');
|
|
await setSetting(req.schema, 'feature_branding', 'false');
|
|
await setSetting(req.schema, 'feature_group_manager', 'false');
|
|
await setSetting(req.schema, 'feature_schedule_manager', 'false');
|
|
return res.json({ success:true, features:{branding:false,groupManager:false,scheduleManager:false,appType:'RosterChirp-Chat'} });
|
|
}
|
|
const match = VALID_CODES[code.trim().toUpperCase()];
|
|
if (!match) return res.status(400).json({ error: 'Invalid registration code' });
|
|
await setSetting(req.schema, 'registration_code', code.trim());
|
|
await setSetting(req.schema, 'app_type', match.appType);
|
|
await setSetting(req.schema, 'feature_branding', match.branding ? 'true' : 'false');
|
|
await setSetting(req.schema, 'feature_group_manager', match.groupManager ? 'true' : 'false');
|
|
await setSetting(req.schema, 'feature_schedule_manager', match.scheduleManager ? 'true' : 'false');
|
|
res.json({ success:true, features:{ branding:match.branding, groupManager:match.groupManager, scheduleManager:match.scheduleManager, appType:match.appType } });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
router.patch('/team', authMiddleware, adminMiddleware, async (req, res) => {
|
|
const { toolManagers } = req.body;
|
|
try {
|
|
if (toolManagers !== undefined) {
|
|
const val = JSON.stringify(toolManagers || []);
|
|
await setSetting(req.schema, 'team_tool_managers', val);
|
|
await setSetting(req.schema, 'team_group_managers', val);
|
|
await setSetting(req.schema, 'team_schedule_managers', val);
|
|
}
|
|
res.json({ success: true });
|
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
});
|
|
|
|
module.exports = router;
|