Chores, Family page, genre search, child accounts, wider Search and Notifications center
build-container / build (push) Successful in 4m6s
build-container / build (push) Successful in 4m6s
- Chores tab with repeats, points and a week/month board - Family page: calendar, chores, shopping, notes, chat and tasks on one start page - Genre search in the Store, Free Books and LibriVox - Child accounts: safe content, daily limit and bedtime, ask a parent - Search now covers notes, chat, contacts, chores, recipes and your books - Notifications center: bell with a per-person list of chat, reminders and announcements - Roadmap, README and tests updated Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf292e3bc7
commit
0383da87f5
38 files changed
+720
-79
No files matched your search
@@ -1,10 +1,11 @@
|
||||
// Free audiobooks: LibriVox search and book details, chapter audio is only ever sent to the recording hosts, each person's shelf and listening position.
|
||||
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
|
||||
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-lv-'));
|
||||
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-lv-')); process.env.LIBRIVOX_INDEX_PAGES = '2';
|
||||
const gif = Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64'), seen = []; let port;
|
||||
const fake = http.createServer((req, res) => {
|
||||
const u = new URL(req.url, 'http://x'), p = u.pathname; seen.push(p + u.search);
|
||||
const J = (o, code = 200) => { res.writeHead(code, { 'content-type': 'application/json' }); res.end(JSON.stringify(o)); };
|
||||
if (p === '/api/feed/audiobooks/' && u.searchParams.get('extended') === '1' && u.searchParams.get('offset') !== null) { const off = +u.searchParams.get('offset'); const mk = (id, title, gs) => ({ id: String(id), title, description: 'A story.', language: 'English', totaltimesecs: 600, authors: [{ first_name: 'A', last_name: 'Writer' }], coverart_jpg: `http://127.0.0.1:${port}/cover.gif`, genres: gs.map((name, i) => ({ id: String(i), name })) }); return J({ books: off === 0 ? [mk(901, 'The Hound Case', ['Crime & Mystery Fiction', 'Published 1900 onward']), mk(902, 'Dragons Abound', ['Fantastic Fiction'])] : [mk(903, 'Rhymes for Bed', ["Children's Fiction", 'Poetry']), mk(901, 'The Hound Case', ['Crime & Mystery Fiction'])] }); }
|
||||
if (p === '/api/feed/audiobooks/') {
|
||||
const book = { id: '55', title: 'Pride & Prejudice', description: '<p>A novel</p>', url_librivox: 'https://librivox.org/pp', language: 'English', copyright_year: '1813', totaltimesecs: 600, authors: [{ first_name: 'Jane', last_name: 'Austen' }], coverart_jpg: `http://127.0.0.1:${port}/cover.gif` };
|
||||
const full = { ...book, sections: [{ section_number: '1', title: 'Chapter 1', listen_url: `http://127.0.0.1:${port}/dl/1.mp3`, playtime: '300', readers: [{ display_name: 'Ann' }] }, { section_number: '2', title: 'Chapter 2', listen_url: 'https://evil.example.com/2.mp3', playtime: '300' }, { section_number: '3', title: 'Chapter 3', listen_url: `http://127.0.0.1:${port}/dl/3.mp3`, playtime: '300' }] };
|
||||
@@ -39,6 +40,13 @@ const fake = http.createServer((req, res) => {
|
||||
await post({ 'lv:55': { pos: 400, dur: 600, t: 10 } }); assert.equal((await post({ 'lv:55': { pos: 5, dur: 600, t: 3 } }))['lv:55'].pos, 400, 'the newest position wins');
|
||||
assert.deepEqual(await j(await call('/progress', { headers: { 'x-user': 'b' } })), {});
|
||||
await call('/shelf/55', { method: 'DELETE' }); assert.deepEqual(await j(await call('/shelf')), []);
|
||||
// genres: gathered once into an index, then searched locally
|
||||
let j2; r = await call('/search?genre=mystery'); j2 = await r.json(); assert.equal(j2.items.length, 0); assert.equal(j2.building, true, 'the first time it says the index is being built');
|
||||
for (let i = 0; i < 40 && !lv.loadIdx(); i++) await new Promise(ok => setTimeout(ok, 100)); assert.ok(lv.loadIdx(), 'the index is built and kept');
|
||||
j2 = await (await call('/search?genre=mystery')).json(); assert.deepEqual(j2.items.map(b => b.id), ['901'], 'a book in two pages is listed once'); assert.equal(j2.items[0].author, 'A Writer'); assert.match(j2.items[0].cover, /^\/api\/ab\/free\/cover\/901/);
|
||||
assert.deepEqual((await (await call('/search?genre=children')).json()).items.map(b => b.id), ['903']); assert.deepEqual((await (await call('/search?genre=poetry')).json()).items.map(b => b.id), ['903'], 'a book can be in several genres'); assert.deepEqual((await (await call('/search?genre=fantasy')).json()).items.map(b => b.id), ['902']);
|
||||
assert.deepEqual((await (await call('/search?genre=fiction&q=hound')).json()).items.map(b => b.id), ['901'], 'words narrow a genre'); assert.deepEqual((await (await call('/search?genre=fiction&q=zebra')).json()).items, []);
|
||||
const gl = await (await call('/genres')).json(); assert.equal(gl.ready, true); assert.equal(gl.genres.find(g => g.id === 'mystery').count, 1); assert.equal(gl.genres.find(g => g.id === 'cooking').count, 0); assert.deepEqual(await (await call('/search?genre=nope')).json(), { items: [], more: false });
|
||||
console.log('librivox test ok');
|
||||
} catch (err) { console.error(err); process.exitCode = 1; }
|
||||
hub.close(); fake.close();
|
||||
|
||||
Reference in new issue
Block a user