openvibers/openvibe.network Commits

Theme API: GET /api/themes/seasons (what stands in for the default theme when, and now) and each seasonal theme in the catalog names its season (#325)

OP OpenVibers committed 19 days ago · e6e36dd648 · 2 files, +29 −0

Changes

ddiff --git a/server/themes/routes.js b/server/themes/routes.js
iindex 90b08732..9fdf0d75 100644
---- a/server/themes/routes.js
++++ b/server/themes/routes.js
@@@ -79,14 +79,32 @@ router.get('/', async (req, res) => {
params.push(Math.min(parseInt(limit, 10) || 100, 500));
const themes = await db.prepare(sql).all(...params);
++ const seasonOf = new Map(seasonList().map((s) => [s.theme, s]));
for (const t of themes) {
try { t.variables = JSON.parse(t.variables); } catch { t.variables = {}; }
try { t.preview_colors = JSON.parse(t.preview_colors); } catch { t.preview_colors = null; }
try { t.tags = JSON.parse(t.tags); } catch { t.tags = []; }
++ const s = seasonOf.get(t.id) || seasonOf.get(t.slug);
++ if (s) t.season = { id: s.id, name: s.name, from: s.from, to: s.to };
}
res.json({ themes });
});
++// ── Seasons: which theme stands in for the default theme when (openvibe-shared builtin-themes SEASONS) ──
++// GET /api/themes/seasons[?date=YYYY-MM-DD] → { seasons: [{ id, name, theme, from, to }], now: { id, name, theme } | null }
++// from/to are MM-DD, both days included, in the visitor's own calendar (the browser decides; `now` here is for the date
++// given, else today in UTC). Anyone on the default theme who has not turned seasonal themes off (display `seasonal`) sees
++// the season's theme, and every OpenVibe mark wears the season (theme-loader.js, ov-mark.js).
++function seasonList() { return (require('openvibe-shared/builtin-themes').SEASONS || []).map((s) => ({ id: s.id, name: s.name, theme: s.theme, from: s.from, to: s.to })); }
++function seasonOn(md, list) { return list.find((s) => (s.from <= s.to ? md >= s.from && md <= s.to : md >= s.from || md <= s.to)) || null; }
++router.get('/seasons', (req, res) => {
++ const list = seasonList();
++ const asked = /^\d{4}-(\d{2})-(\d{2})$/.exec(String(req.query.date || ''));
++ const md = asked ? `${asked[1]}-${asked[2]}` : new Date().toISOString().slice(5, 10);
++ const now = seasonOn(md, list);
++ res.set('Cache-Control', 'public, max-age=3600').json({ seasons: list, now: now ? { id: now.id, name: now.name, theme: now.theme } : null });
++});
++
// ── Get User's Active Theme ──────────────────────────────────
// A request with no credentials at all is a guest asking "do I have a theme?": answer "no" with
// a 200 so every page view by a guest does not log a failed request in the console.
ddiff --git a/test/themes-review.test.js b/test/themes-review.test.js
iindex 53fa74c4..263ac175 100644
---- a/test/themes-review.test.js
++++ b/test/themes-review.test.js
@@@ -109,6 +109,17 @@ const token = async (id) => signToken(await db.prepare('SELECT * FROM users WHER
r = await call('PUT', '/api/themes/me/display', { as: 2, body: { seasonal: 'maybe' } });
assert.ok(!('seasonal' in (r.body.display || {})), 'only on or off');
++ // ── Seasons: what stands in for the default theme when, for apps and the SDK ──
++ r = await call('GET', '/api/themes/seasons?date=2026-10-25');
++ assert.deepStrictEqual(r.body.seasons.map((x) => [x.id, x.theme, x.from, x.to]), [['halloween', 'halloween', '10-01', '10-31'], ['winter', 'frost', '12-01', '12-31']]);
++ assert.deepStrictEqual(r.body.now, { id: 'halloween', name: 'Halloween', theme: 'halloween' });
++ assert.strictEqual((await call('GET', '/api/themes/seasons?date=2026-11-05')).body.now, null);
++ assert.strictEqual((await call('GET', '/api/themes/seasons?date=2026-12-24')).body.now.id, 'winter');
++ const cat = (await call('GET', '/api/themes')).body.themes;
++ const pumpkin = cat.find((t) => t.id === 'halloween');
++ assert.ok(!pumpkin || (pumpkin.season && pumpkin.season.id === 'halloween'), 'a seasonal theme in the catalog says its season');
++ assert.ok(cat.filter((t) => t.season).every((t) => ['halloween', 'frost'].includes(t.id)), 'and only those');
++
server.close();
fs.rmSync(dir, { recursive: true, force: true });
console.log('themes review: all checks passed');