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 { getDb } = require('../models/db'); const { authMiddleware, adminMiddleware } = require('../middleware/auth'); // Generic icon storage factory function makeIconStorage(prefix) { return multer.diskStorage({ destination: '/app/uploads/logos', filename: (req, file, cb) => { const ext = path.extname(file.originalname); cb(null, `${prefix}_${Date.now()}${ext}`); } }); } const iconUploadOpts = { limits: { fileSize: 1 * 1024 * 1024 }, fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) cb(null, true); else cb(new Error('Images only')); } }; const uploadLogo = multer({ storage: makeIconStorage('logo'), ...iconUploadOpts }); const uploadNewChat = multer({ storage: makeIconStorage('newchat'), ...iconUploadOpts }); const uploadGroupInfo = multer({ storage: makeIconStorage('groupinfo'), ...iconUploadOpts }); // Get public settings (accessible by all) router.get('/', (req, res) => { const db = getDb(); const settings = db.prepare('SELECT key, value FROM settings').all(); const obj = {}; for (const s of settings) obj[s.key] = s.value; const admin = db.prepare('SELECT email FROM users WHERE is_default_admin = 1').get(); if (admin) obj.admin_email = admin.email; // Expose app version from Docker build arg env var obj.app_version = process.env.JAMA_VERSION || process.env.TEAMCHAT_VERSION || 'dev'; obj.user_pass = process.env.USER_PASS || 'user@1234'; res.json({ settings: obj }); }); // Update app name (admin) router.patch('/app-name', authMiddleware, adminMiddleware, (req, res) => { const { name } = req.body; if (!name?.trim()) return res.status(400).json({ error: 'Name required' }); const db = getDb(); db.prepare("UPDATE settings SET value = ?, updated_at = datetime('now') WHERE key = 'app_name'").run(name.trim()); res.json({ success: true, name: name.trim() }); }); // Upload app logo (admin) — also generates 192x192 and 512x512 PWA icons 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}`; const srcPath = req.file.path; try { // Generate PWA icons from the uploaded logo const icon192Path = '/app/uploads/logos/pwa-icon-192.png'; const icon512Path = '/app/uploads/logos/pwa-icon-512.png'; await sharp(srcPath) .resize(192, 192, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) .png() .toFile(icon192Path); await sharp(srcPath) .resize(512, 512, { fit: 'contain', background: { r: 255, g: 255, b: 255, alpha: 0 } }) .png() .toFile(icon512Path); const db = getDb(); db.prepare("UPDATE settings SET value = ?, updated_at = datetime('now') WHERE key = 'logo_url'").run(logoUrl); // Store the PWA icon paths so the manifest can reference them db.prepare("INSERT INTO settings (key, value) VALUES ('pwa_icon_192', ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')") .run('/uploads/logos/pwa-icon-192.png', '/uploads/logos/pwa-icon-192.png'); db.prepare("INSERT INTO settings (key, value) VALUES ('pwa_icon_512', ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')") .run('/uploads/logos/pwa-icon-512.png', '/uploads/logos/pwa-icon-512.png'); res.json({ logoUrl }); } catch (err) { console.error('[Logo] Failed to generate PWA icons:', err.message); // Still save the logo even if icon generation fails const db = getDb(); db.prepare("UPDATE settings SET value = ?, updated_at = datetime('now') WHERE key = 'logo_url'").run(logoUrl); res.json({ logoUrl }); } }); // Upload New Chat icon (admin) router.post('/icon-newchat', authMiddleware, adminMiddleware, uploadNewChat.single('icon'), (req, res) => { if (!req.file) return res.status(400).json({ error: 'No file' }); const iconUrl = `/uploads/logos/${req.file.filename}`; const db = getDb(); db.prepare("INSERT INTO settings (key, value) VALUES ('icon_newchat', ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')") .run(iconUrl, iconUrl); res.json({ iconUrl }); }); // Upload Group Info icon (admin) router.post('/icon-groupinfo', authMiddleware, adminMiddleware, uploadGroupInfo.single('icon'), (req, res) => { if (!req.file) return res.status(400).json({ error: 'No file' }); const iconUrl = `/uploads/logos/${req.file.filename}`; const db = getDb(); db.prepare("INSERT INTO settings (key, value) VALUES ('icon_groupinfo', ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')") .run(iconUrl, iconUrl); res.json({ iconUrl }); }); // Reset all settings to defaults (admin) router.patch('/colors', authMiddleware, adminMiddleware, (req, res) => { const { colorTitle, colorTitleDark, colorAvatarPublic, colorAvatarDm } = req.body; const db = getDb(); const upd = db.prepare("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = datetime('now')"); if (colorTitle !== undefined) upd.run('color_title', colorTitle || '', colorTitle || ''); if (colorTitleDark !== undefined) upd.run('color_title_dark', colorTitleDark || '', colorTitleDark || ''); if (colorAvatarPublic !== undefined) upd.run('color_avatar_public', colorAvatarPublic || '', colorAvatarPublic || ''); if (colorAvatarDm !== undefined) upd.run('color_avatar_dm', colorAvatarDm || '', colorAvatarDm || ''); res.json({ success: true }); }); router.post('/reset', authMiddleware, adminMiddleware, (req, res) => { const db = getDb(); const originalName = process.env.APP_NAME || 'jama'; db.prepare("UPDATE settings SET value = ?, updated_at = datetime('now') WHERE key = 'app_name'").run(originalName); db.prepare("UPDATE settings SET value = '', updated_at = datetime('now') WHERE key = 'logo_url'").run(); db.prepare("UPDATE settings SET value = '', updated_at = datetime('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')").run(); res.json({ success: true }); }); module.exports = router;