openvibers/openvibe.blog Commits

Blog collections load their posts' tags in parallel, not one query each (#169)

OP OpenVibers committed 12 days ago · fa8303906f · 2 files, +17 −12

Changes

ddiff --git a/server/domain/reading.js b/server/domain/reading.js
iindex 07646ac..14b79ff 100644
---- a/server/domain/reading.js
++++ b/server/domain/reading.js
@@@ -41,33 +41,31 @@ function createReading({ store, blogs, posts: postsApi, publication, people, med
};
}
-- /** Blogs of these rows, one lookup per distinct blog. */
++ /** Blogs of these rows, one lookup per distinct blog, all at once. */
async function blogsFor(rows) {
-- const out = new Map();
-- for (const id of new Set(rows.map((p) => p.blog_id))) out.set(id, await blogs.get(id));
-- return out;
++ const ids = [...new Set(rows.map((p) => p.blog_id))];
++ return new Map(await Promise.all(ids.map(async (id) => [id, await blogs.get(id)])));
}
const revRefs = (rows) => rows.map((p) => ({ entityId: p.id, revision: p.published_revision }));
-- /** List items for rows of published posts (possibly from several blogs): revisions in one query (no N+1). */
++ /** List items for rows of published posts (possibly from several blogs): revisions in one query (no N+1), each post's tags in parallel. */
async function listItems(rows, { perBlogLinks = true } = {}) {
const [who, revs, blogById] = await Promise.all([people.many(rows.map((p) => p.author_subject)), store.revisions.getMany(revRefs(rows)), blogsFor(rows)]);
-- const out = [];
-- for (const [i, post] of rows.entries()) {
++ const out = await Promise.all(rows.map(async (post, i) => {
const rev = revs[i];
-- if (!rev) continue;
++ if (!rev) return null;
const blog = blogById.get(post.blog_id);
const person = who.get(post.author_subject);
const { tags } = await termsView(post, blog, { networkTags: !perBlogLinks });
-- out.push({
++ return {
post, rev, blog,
url: publication.postPath(blog, post),
author: person ? { name: person.name, url: urls.author(person, blog) } : null,
tags,
badge: { members: 'Members only', private: 'Private' }[post.visibility] || null,
-- });
-- }
-- return out;
++ };
++ }));
++ return out.filter(Boolean);
}
async function categoriesTree(blog) {
ddiff --git a/test/home.test.js b/test/home.test.js
iindex 914d026..9cc52f8 100644
---- a/test/home.test.js
++++ b/test/home.test.js
@@@ -84,6 +84,13 @@ const ld = (text) => [...text.matchAll(/<script type="application\/ld\+json">([\
for (const q of faq.mainEntity) assert.ok(text.includes(`<summary>${q.name.replace(/'/g, '&#39;')}</summary>`), q.name);
});
++ await check('a tag page: every post tagged so, newest first, each carrying its tag links', async () => {
++ const r = await t.get('/tags/patch-notes');
++ assert.strictEqual(r.status, 200);
++ assert.ok(r.text.indexOf('Patch notes: The Rundown') < r.text.indexOf('Patch notes: Voice channels'), 'newest first');
++ assert.ok(r.text.includes('<a href="/tags/patch-notes">#patch-notes</a>') && r.text.includes('<a href="/tags/chat">#chat</a>'), 'each post keeps its tags');
++ });
++
await check('an empty network: sections with nothing to show are left out', async () => {
await db.prepare('DELETE FROM changelog_entries').run();
await db.prepare('DELETE FROM blog_desk_posts').run();