feat: #13255, add category name and handle to category search zset

This commit is contained in:
Julian Lam
2025-03-19 23:04:43 -04:00
parent bfc7daf255
commit 876d1b0414
2 changed files with 75 additions and 65 deletions

View File

@@ -274,7 +274,7 @@ Actors.assertGroup = async (ids, options = {}) => {
activitypub.helpers.log(`[activitypub/actors] Asserting ${ids.length} group(s)`); 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 urlMap = new Map();
const followersUrlMap = new Map(); const followersUrlMap = new Map();
@@ -312,10 +312,10 @@ Actors.assertGroup = async (ids, options = {}) => {
return actor; return actor;
} catch (e) { } catch (e) {
if (e.code === 'ap_get_410') { if (e.code === 'ap_get_410') {
// const exists = await user.exists(id); const exists = await categories.exists(id);
// if (exists) { if (exists) {
// await user.deleteAccount(id); await categories.purge(id, 0);
// } }
} }
return null; return null;
@@ -343,7 +343,7 @@ Actors.assertGroup = async (ids, options = {}) => {
const cidsForCurrent = categoryObjs.map((p, idx) => (exists[idx] ? p.cid : 0)); const cidsForCurrent = categoryObjs.map((p, idx) => (exists[idx] ? p.cid : 0));
const current = await categories.getCategoriesFields(cidsForCurrent, ['slug']); const current = await categories.getCategoriesFields(cidsForCurrent, ['slug']);
const queries = categoryObjs.reduce((memo, profile, idx) => { const queries = categoryObjs.reduce((memo, profile, idx) => {
const { slug } = current[idx]; const { slug, name } = current[idx];
if (options.update || slug !== profile.slug) { if (options.update || slug !== profile.slug) {
if (cidsForCurrent[idx] !== 0 && slug) { if (cidsForCurrent[idx] !== 0 && slug) {
@@ -351,31 +351,31 @@ Actors.assertGroup = async (ids, options = {}) => {
memo.handleRemove.push(slug.toLowerCase()); 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; memo.handleAdd[profile.slug.toLowerCase()] = profile.cid;
} }
// if (options.update || (profile.fullname && fullname !== profile.fullname)) { if (options.update || (profile.name && name !== profile.name)) {
// if (fullname && cidsForCurrent[idx] !== 0) { if (name && cidsForCurrent[idx] !== 0) {
// memo.searchRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${profile.uid}`]); 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; return memo;
}, { /* searchRemove: [], searchAdd: [], */ handleRemove: [], handleAdd: {} }); }, { searchRemove: [], searchAdd: [], handleRemove: [], handleAdd: {} });
// Removals // Removals
await Promise.all([ await Promise.all([
// db.sortedSetRemoveBulk(queries.searchRemove), db.sortedSetRemoveBulk(queries.searchRemove),
db.deleteObjectFields('handle:cid', queries.handleRemove), db.deleteObjectFields('handle:cid', queries.handleRemove),
]); ]);
await Promise.all([ await Promise.all([
db.setObjectBulk(bulkSet), db.setObjectBulk(bulkSet),
db.sortedSetAdd('usersRemote:lastCrawled', groups.map(() => now), groups.map(p => p.id)), 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), db.setObject('handle:cid', queries.handleAdd),
_migratePersonToGroup(categoryObjs), _migratePersonToGroup(categoryObjs),
]); ]);
@@ -508,18 +508,18 @@ Actors.removeGroup = async (id) => {
return false; 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(); slug = slug.toLowerCase();
// const bulkRemove = [ const bulkRemove = [
// ['ap.preferredUsername:sorted', `${name}:${id}`], ['categories:name', `${slug}:${id}`],
// ]; ];
// if (fullname) { if (name) {
// bulkRemove.push(['ap.name:sorted', `${fullname.toLowerCase()}:${id}`]); bulkRemove.push(['categories:name', `${name.toLowerCase()}:${id}`]);
// } }
await Promise.all([ await Promise.all([
// db.sortedSetRemoveBulk(bulkRemove), db.sortedSetRemoveBulk(bulkRemove),
db.deleteObjectField('handle:cid', slug), db.deleteObjectField('handle:cid', slug),
db.deleteObjectField('followersUrl:cid', followersUrl), db.deleteObjectField('followersUrl:cid', followersUrl),
db.deleteObjectField('remoteUrl:cid', url), db.deleteObjectField('remoteUrl:cid', url),

View File

@@ -130,8 +130,54 @@ describe('Actor asserton', () => {
assert.strictEqual(userRemoteHashExists, false); 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 () => { it('should delete a remote category when Categories.purge is called', async () => {
const { id } = helpers.mocks.group(); const { id } = helpers.mocks.group();
await activitypub.actors.assertGroup([id]); 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 () => { it('should also delete AP-specific keys that were added by assertGroup', async () => {
const { id } = helpers.mocks.group(); const { id } = helpers.mocks.group();
const assertion = await activitypub.actors.assertGroup([id]); const assertion = await activitypub.actors.assertGroup([id]);
const [{ slug }] = assertion; const [{ handle, slug }] = assertion;
await categories.purge(id, 0); 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(!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('Controllers', () => {
describe('User Actor endpoint', () => { describe('User Actor endpoint', () => {
let uid; let uid;