From dcacc3f1102a862ccb8bdeeb76d27c28a250f484 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 19 Mar 2025 23:04:43 -0400 Subject: [PATCH] feat: #13255, add category name and handle to category search zset --- src/activitypub/actors.js | 48 ++++++++++---------- test/activitypub/actors.js | 92 +++++++++++++++++++++----------------- 2 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index c89e381f45..b13f35ed18 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -274,7 +274,7 @@ Actors.assertGroup = async (ids, options = {}) => { activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} group(s)`); - // NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVE! + // NOTE: MAKE SURE EVERY DB ADDITION HAS A CORRESPONDING REMOVAL IN ACTORS.REMOVEGROUP! const urlMap = new Map(); const followersUrlMap = new Map(); @@ -312,10 +312,10 @@ Actors.assertGroup = async (ids, options = {}) => { return actor; } catch (e) { if (e.code === 'ap_get_410') { - // const exists = await user.exists(id); - // if (exists) { - // await user.deleteAccount(id); - // } + const exists = await categories.exists(id); + if (exists) { + await categories.purge(id, 0); + } } return null; @@ -343,7 +343,7 @@ Actors.assertGroup = async (ids, options = {}) => { const cidsForCurrent = categoryObjs.map((p, idx) => (exists[idx] ? p.cid : 0)); const current = await categories.getCategoriesFields(cidsForCurrent, ['slug']); const queries = categoryObjs.reduce((memo, profile, idx) => { - const { slug } = current[idx]; + const { slug, name } = current[idx]; if (options.update || slug !== profile.slug) { if (cidsForCurrent[idx] !== 0 && slug) { @@ -351,31 +351,31 @@ Actors.assertGroup = async (ids, options = {}) => { memo.handleRemove.push(slug.toLowerCase()); } - // memo.searchAdd.push(['ap.preferredUsername:sorted', 0, `${profile.slug.toLowerCase()}:${profile.uid}`]); + memo.searchAdd.push(['categories:name', 0, `${profile.slug.slice(0, 200).toLowerCase()}:${profile.cid}`]); memo.handleAdd[profile.slug.toLowerCase()] = profile.cid; } - // if (options.update || (profile.fullname && fullname !== profile.fullname)) { - // if (fullname && cidsForCurrent[idx] !== 0) { - // memo.searchRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]); - // } + if (options.update || (profile.name && name !== profile.name)) { + if (name && cidsForCurrent[idx] !== 0) { + memo.searchRemove.push(['categories:name', `${name.toLowerCase()}:${profile.cid}`]); + } - // memo.searchAdd.push(['ap.name:sorted', 0, `${profile.fullname.toLowerCase()}:${profile.uid}`]); - // } + memo.searchAdd.push(['categories:name', 0, `${profile.name.toLowerCase()}:${profile.cid}`]); + } return memo; - }, { /* searchRemove: [], searchAdd: [], */ handleRemove: [], handleAdd: {} }); + }, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} }); // Removals await Promise.all([ - // db.sortedSetRemoveBulk(queries.searchRemove), + db.sortedSetRemoveBulk(queries.searchRemove), db.deleteObjectFields('handle:cid', queries.handleRemove), ]); await Promise.all([ db.setObjectBulk(bulkSet), db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)), - // db.sortedSetAddBulk(queries.searchAdd), + db.sortedSetAddBulk(queries.searchAdd), db.setObject('handle:cid', queries.handleAdd), _migratePersonToGroup(categoryObjs), ]); @@ -508,18 +508,18 @@ Actors.removeGroup = async (id) => { return false; } - let { slug, /* fullname, */url, followersUrl } = await categories.getCategoryFields(id, ['slug', /* 'fullname', */ 'url', 'followersUrl']); + let { slug, name, url, followersUrl } = await categories.getCategoryFields(id, ['slug', 'name', 'url', 'followersUrl']); slug = slug.toLowerCase(); - // const bulkRemove = [ - // ['ap.preferredUsername:sorted', `${name}:${id}`], - // ]; - // if (fullname) { - // bulkRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${id}`]); - // } + const bulkRemove = [ + ['categories:name', `${slug}:${id}`], + ]; + if (name) { + bulkRemove.push(['categories:name', `${name.toLowerCase()}:${id}`]); + } await Promise.all([ - // db.sortedSetRemoveBulk(bulkRemove), + db.sortedSetRemoveBulk(bulkRemove), db.deleteObjectField('handle:cid', slug), db.deleteObjectField('followersUrl:cid', followersUrl), db.deleteObjectField('remoteUrl:cid', url), diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 01785ecfcb..ec15ec4191 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -130,8 +130,54 @@ describe('Actor asserton', () => { assert.strictEqual(userRemoteHashExists, false); }); }); +}); - describe('deletion', () => { +describe('Group assertion', () => { + let actorUri; + let actorData; + + before(async () => { + const { id, actor } = helpers.mocks.group(); + actorUri = id; + actorData = actor; + }); + + it('should assert a uri identifying as "Group" into a remote category', async () => { + const assertion = await activitypub.actors.assertGroup([actorUri]); + + assert(assertion, Array.isArray(assertion)); + assert.strictEqual(assertion.length, 1); + + const category = assertion.pop(); + assert.strictEqual(category.cid, actorUri); + }); + + it('should be considered existing when checked', async () => { + const exists = await categories.exists(actorUri); + + assert(exists); + }); + + it('should contain an entry in categories search zset', async () => { + const exists = await db.isSortedSetMember('categories:name', `${actorData.preferredUsername.toLowerCase()}:${actorUri}`); + + assert(exists); + }); + + it('should return category data when getter methods are called', async () => { + const category = await categories.getCategoryData(actorUri); + assert(category); + assert.strictEqual(category.cid, actorUri); + }); + + it('should not assert non-group users when called', async () => { + const { id } = helpers.mocks.person(); + const assertion = await activitypub.actors.assertGroup([id]); + + assert(Array.isArray(assertion) && !assertion.length); + }); + + describe.only('deletion', () => { it('should delete a remote category when Categories.purge is called', async () => { const { id } = helpers.mocks.group(); await activitypub.actors.assertGroup([id]); @@ -151,54 +197,18 @@ describe('Actor asserton', () => { it('should also delete AP-specific keys that were added by assertGroup', async () => { const { id } = helpers.mocks.group(); const assertion = await activitypub.actors.assertGroup([id]); - const [{ slug }] = assertion; + const [{ handle, slug }] = assertion; await categories.purge(id, 0); - const isMember = await db.isObjectField('handle:cid', slug); + const isMember = await db.isObjectField('handle:cid', handle); + const inSearch = await db.isSortedSetMember('categories:name', `${slug}:${id}`); assert(!isMember); + assert(!inSearch); }); }); }); -describe('Group assertion', () => { - let actorUri; - - before(async () => { - const { id, actor } = helpers.mocks.group(); - actorUri = id; - }); - - it('should assert a uri identifying as "Group" into a remote category', async () => { - const assertion = await activitypub.actors.assertGroup([actorUri]); - - assert(assertion, Array.isArray(assertion)); - assert.strictEqual(assertion.length, 1); - - const category = assertion.pop(); - assert.strictEqual(category.cid, actorUri); - }); - - it('should be considered existing when checked', async () => { - const exists = await categories.exists(actorUri); - - assert(exists); - }); - - it('should return category data when getter methods are called', async () => { - const category = await categories.getCategoryData(actorUri); - assert(category); - assert.strictEqual(category.cid, actorUri); - }); - - it('should not assert non-group users when called', async () => { - const { id } = helpers.mocks.person(); - const assertion = await activitypub.actors.assertGroup([id]); - - assert(Array.isArray(assertion) && !assertion.length); - }); -}); - describe('Controllers', () => { describe('User Actor endpoint', () => { let uid;