Add school name and logo to home screen and admin header
- School name now appears on the student home screen and updates the page title - Logo can be uploaded in Settings (PNG/JPG/GIF/WebP/SVG, max 2 MB); stored in the data volume so it persists across container restarts - When a logo is uploaded the text school name is hidden in favour of the logo - Admin header shows the school name instead of hardcoded "Homeschool", updates live when saved - New server endpoints: GET /api/logo, POST /api/admin/upload-logo, DELETE /api/admin/logo - /api/public-settings now includes school_name and has_logo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
1eedfc7715
commit
6b7baf7297
6 files changed
+135
-5
No files matched your search
@@ -3,7 +3,7 @@ import { join, dirname, extname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { readFile, writeFile, unlink } from 'node:fs/promises';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import mammoth from 'mammoth';
|
||||
import multer from 'multer';
|
||||
@@ -711,7 +711,56 @@ app.post('/api/admin/app-settings', requirePin, (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/api/public-settings', (req, res) => {
|
||||
res.json({ show_home_emoji: getSetting('show_home_emoji') !== 'false' });
|
||||
const logoExt = getSetting('logo_ext');
|
||||
res.json({
|
||||
show_home_emoji: getSetting('show_home_emoji') !== 'false',
|
||||
school_name: getSetting('school_name') || '',
|
||||
has_logo: !!(logoExt && existsSync(join(DATA_DIR, `logo${logoExt}`))),
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/logo', (req, res) => {
|
||||
const ext = getSetting('logo_ext');
|
||||
if (!ext) return res.status(404).end();
|
||||
const filePath = join(DATA_DIR, `logo${ext}`);
|
||||
if (!existsSync(filePath)) return res.status(404).end();
|
||||
const mimeMap = { '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml' };
|
||||
res.setHeader('Content-Type', mimeMap[ext] || 'application/octet-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.sendFile(filePath);
|
||||
});
|
||||
|
||||
app.post('/api/admin/upload-logo', requirePin, async (req, res) => {
|
||||
const { base64: b64, mime } = req.body;
|
||||
if (!b64 || !mime) return res.status(400).json({ error: 'base64 and mime required' });
|
||||
const mimeToExt = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/gif': '.gif', 'image/webp': '.webp', 'image/svg+xml': '.svg' };
|
||||
const ext = mimeToExt[mime];
|
||||
if (!ext) return res.status(400).json({ error: 'Unsupported image type' });
|
||||
|
||||
let buffer;
|
||||
try { buffer = Buffer.from(b64, 'base64'); }
|
||||
catch (_) { return res.status(400).json({ error: 'Invalid base64' }); }
|
||||
|
||||
if (buffer.byteLength > 2 * 1024 * 1024) return res.status(400).json({ error: 'Logo must be under 2 MB' });
|
||||
|
||||
const oldExt = getSetting('logo_ext');
|
||||
if (oldExt && oldExt !== ext) {
|
||||
await unlink(join(DATA_DIR, `logo${oldExt}`)).catch(() => {});
|
||||
}
|
||||
|
||||
await writeFile(join(DATA_DIR, `logo${ext}`), buffer);
|
||||
const upsert = (k, v) => db.prepare(`INSERT INTO settings (key,value) VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run(k, String(v));
|
||||
upsert('logo_ext', ext);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
app.delete('/api/admin/logo', requirePin, async (req, res) => {
|
||||
const ext = getSetting('logo_ext');
|
||||
if (ext) {
|
||||
await unlink(join(DATA_DIR, `logo${ext}`)).catch(() => {});
|
||||
db.prepare(`DELETE FROM settings WHERE key = 'logo_ext'`).run();
|
||||
}
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// Validate MM-DD format (no year stored — birthday repeats annually)
|
||||
|
||||
Reference in new issue
Block a user