Movies & TV: favorites, a family movie night list to vote on, and mixes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-27 10:20:33 -04:00
1 parent fe651a8c37
commit 2766424bb2
7 files changed
+107 -2

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
// Movies & TV for the family: favorites are per person; Movie night is one shared list with votes.
const assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path'), http = require('http');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-mf-'));
const users = require('../users'), mf = require('../moviefam');
const ann = users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), bo = users.create({ name: 'Bo', role: 'user', tabs: ['video'], handle: 'ZP' });
const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] === 'bo' ? bo : ann, () => mf.handle(req, res))).listen(0, async () => {
const base = 'http://127.0.0.1:' + hub.address().port + '/api/moviefam', j = r => r.json(), post = (p, o, who) => fetch(base + p, { method: 'POST', body: JSON.stringify(o), headers: who ? { 'x-user': who } : {} }), get = (p, who) => fetch(base + p, { headers: who ? { 'x-user': who } : {} });
const up = { id: '101', title: 'Up', year: 2009, type: 'movie', thumb: '/library/metadata/101/thumb/17' };
try {
// favorites
await post('/fav', { ...up, on: true }); assert.deepEqual((await j(await get('/favs'))).items.map(x => x.title), ['Up']); assert.equal((await j(await get('/favs', 'bo'))).items.length, 0, 'favorites are personal');
await post('/fav', { ...up, on: false }); assert.equal((await j(await get('/favs'))).items.length, 0);
assert.equal((await post('/fav', { id: '../x', title: 'x', on: true })).status, 400, 'a bad id is refused');
// a poster path that is not ours is dropped
await post('/fav', { ...up, thumb: 'https://evil.example/x.jpg', on: true }); assert.equal((await j(await get('/favs'))).items[0].thumb, '');
// movie night
let r = await j(await post('/night/add', up)); assert.equal(r.items[0].votes, 1, 'the person who adds it votes for it'); assert.equal((await post('/night/add', up)).status, 400, 'once only');
await post('/night/add', { id: '102', title: 'Ratatouille', year: 2007 }, 'bo'); r = await j(await post('/night/vote', { id: '102' }));
assert.equal(r.items[0].title, 'Ratatouille', 'the one with more votes comes first'); assert.deepEqual(r.items[0].voters.sort(), ['Ann', 'Bo']);
r = await j(await post('/night/vote', { id: '102' })); assert.equal(r.items.find(x => x.id === '102').votes, 1, 'voting again takes the vote back');
assert.equal((await post('/night/remove', { id: '101' }, 'bo')).status, 403, 'only the adder or an admin removes'); r = await j(await post('/night/remove', { id: '102' })); assert.equal(r.items.length, 1, 'an admin can remove anything');
assert.equal((await post('/night/clear', {}, 'bo')).status, 403); assert.equal((await j(await post('/night/clear', {}))).items.length, 0);
console.log('moviefam test ok');
} catch (e) { console.error(e); process.exitCode = 1; }
hub.close();
});