fix: sync follow counts on local and remote follows, #14105

This commit is contained in:
Julian Lam
2026-03-18 12:13:50 -04:00
parent cc606677cb
commit 44e78e4775
4 changed files with 39 additions and 25 deletions

View File

@@ -473,7 +473,7 @@ Helpers.generateCollection = async ({ set, method, count, page, perPage, url })
} else if (set) { } else if (set) {
method = method.bind(null, set); method = method.bind(null, set);
} }
count = count || await db.sortedSetCard(set); count = count ?? await db.sortedSetCard(set);
const pageCount = Math.max(1, Math.ceil(count / perPage)); const pageCount = Math.max(1, Math.ceil(count / perPage));
let items = []; let items = [];
let paginate = true; let paginate = true;

View File

@@ -517,11 +517,12 @@ inbox.follow = async (req) => {
} }
const now = Date.now(); const now = Date.now();
await db.sortedSetAdd(`followersRemote:${id}`, now, actor); await Promise.all([
await db.sortedSetAdd(`followingRemote:${actor}`, now, id); // for following backreference (actor pruning) db.sortedSetAdd(`followersRemote:${id}`, now, actor),
db.sortedSetAdd(`followingRemote:${actor}`, now, id), // for following backreference (actor pruning)
const followerRemoteCount = await db.sortedSetCard(`followersRemote:${id}`); user.syncFollowCounts(id, false, true),
await user.setUserField(id, 'followerRemoteCount', followerRemoteCount); user.syncFollowCounts(actor, true, false),
]);
activitypub.actors._followerCache.del(id); activitypub.actors._followerCache.del(id);
await user.onFollow(actor, id); await user.onFollow(actor, id);
@@ -600,8 +601,8 @@ inbox.accept = async (req) => {
db.sortedSetAdd(`followingRemote:${id}`, timestamp, actor), db.sortedSetAdd(`followingRemote:${id}`, timestamp, actor),
db.sortedSetAdd(`followersRemote:${actor}`, timestamp, id), // for followers backreference and notes assertion checking db.sortedSetAdd(`followersRemote:${actor}`, timestamp, id), // for followers backreference and notes assertion checking
]); ]);
const followingRemoteCount = await db.sortedSetCard(`followingRemote:${id}`); await user.syncFollowCounts(id, true, false);
await user.setUserField(id, 'followingRemoteCount', followingRemoteCount); await user.syncFollowCounts(actor, false, true);
} else if (localType === 'category') { } else if (localType === 'category') {
if (!await db.isSortedSetMember(`followRequests:cid.${id}`, actor)) { if (!await db.isSortedSetMember(`followRequests:cid.${id}`, actor)) {
if (await db.isSortedSetMember(`cid:${id}:following`, actor)) return; // already following if (await db.isSortedSetMember(`cid:${id}:following`, actor)) return; // already following
@@ -649,9 +650,9 @@ inbox.undo = async (req) => {
await Promise.all([ await Promise.all([
db.sortedSetRemove(`followersRemote:${id}`, actor), db.sortedSetRemove(`followersRemote:${id}`, actor),
db.sortedSetRemove(`followingRemote:${actor}`, id), db.sortedSetRemove(`followingRemote:${actor}`, id),
user.syncFollowCounts(id, false, true),
user.syncFollowCounts(actor, true, false),
]); ]);
const followerRemoteCount = await db.sortedSetCard(`followerRemote:${id}`);
await user.setUserField(id, 'followerRemoteCount', followerRemoteCount);
notifications.rescind(`follow:${id}:uid:${actor}`); notifications.rescind(`follow:${id}:uid:${actor}`);
activitypub.actors._followerCache.del(id); activitypub.actors._followerCache.del(id);
break; break;

View File

@@ -66,10 +66,8 @@ Controller.fetch = async (req, res, next) => {
}; };
Controller.getFollowing = async (req, res) => { Controller.getFollowing = async (req, res) => {
const { followingCount, followingRemoteCount } = await user.getUserFields(req.params.uid, ['followingCount', 'followingRemoteCount']); const followingCount = await user.getUserField(req.params.uid, 'followingCount');
const totalItems = parseInt(followingCount || 0, 10) + parseInt(followingRemoteCount || 0, 10); const count = parseInt(followingCount, 10);
const count = totalItems;
const collection = await activitypub.helpers.generateCollection({ const collection = await activitypub.helpers.generateCollection({
method: user.getFollowing.bind(null, req.params.uid), method: user.getFollowing.bind(null, req.params.uid),
count, count,
@@ -92,10 +90,8 @@ Controller.getFollowing = async (req, res) => {
}; };
Controller.getFollowers = async (req, res) => { Controller.getFollowers = async (req, res) => {
const { followerCount, followerRemoteCount } = await user.getUserFields(req.params.uid, ['followerCount', 'followerRemoteCount']); const followerCount = await user.getUserField(req.params.uid, 'followerCount');
const totalItems = parseInt(followerCount || 0, 10) + parseInt(followerRemoteCount || 0, 10); const count = parseInt(followerCount, 10);
const count = totalItems;
const collection = await activitypub.helpers.generateCollection({ const collection = await activitypub.helpers.generateCollection({
method: user.getFollowers.bind(null, req.params.uid), method: user.getFollowers.bind(null, req.params.uid),
count, count,

View File

@@ -58,15 +58,32 @@ module.exports = function (User) {
]); ]);
} }
const [followingCount, followingRemoteCount, followerCount, followerRemoteCount] = await db.sortedSetsCard([ User.syncFollowCounts(uid, true, false);
`following:${uid}`, `followingRemote:${uid}`, `followers:${theiruid}`, `followersRemote:${theiruid}`, User.syncFollowCounts(theiruid, false, true);
]);
await Promise.all([
User.setUserField(uid, 'followingCount', followingCount + followingRemoteCount),
User.setUserField(theiruid, 'followerCount', followerCount + followerRemoteCount),
]);
} }
User.syncFollowCounts = async function (uid, following, followers) {
const sets = [];
const property = [];
if (following) {
property.push('followingCount');
sets.push(`following:${uid}`, `followingRemote:${uid}`);
}
if (followers) {
property.push('followerCount');
sets.push(`followers:${uid}`, `followersRemote:${uid}`);
};
const values = await db.sortedSetsCard(sets);
const payload = property.reduce((payload, cur, idx) => {
const sum = values[idx * 2] + values[(idx * 2) + 1];
payload[cur] = sum;
return payload;
}, {});
await User.setUserFields(uid, payload);
};
User.getFollowing = async function (uid, start, stop) { User.getFollowing = async function (uid, start, stop) {
return await getFollow(uid, 'following', start, stop); return await getFollow(uid, 'following', start, stop);
}; };