fix: follow/unfollow fails for remotely resolved profiles
POST /accounts/:id/follow returned 404 for actors resolved via Fedify (like @_followback@tags.pub) because resolveActorUrl only checked local data (followers/following/timeline). These actors aren't in local collections — they were resolved on-demand via WebFinger. Fix: add reverse lookup map (accountId hash → actorUrl) to the account cache. When resolveRemoteAccount resolves a profile, the hash-to-URL mapping is stored alongside the stats. resolveActorUrl checks this cache before scanning local collections.
This commit is contained in:
@@ -7,12 +7,18 @@
|
|||||||
* LRU-style with TTL — entries expire after 1 hour.
|
* LRU-style with TTL — entries expire after 1 hour.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { remoteActorId } from "./id-mapping.js";
|
||||||
|
|
||||||
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
||||||
const MAX_ENTRIES = 500;
|
const MAX_ENTRIES = 500;
|
||||||
|
|
||||||
// Map<actorUrl, { followersCount, followingCount, statusesCount, createdAt, cachedAt }>
|
// Map<actorUrl, { followersCount, followingCount, statusesCount, createdAt, cachedAt }>
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
|
|
||||||
|
// Reverse map: accountId (hash) → actorUrl
|
||||||
|
// Populated alongside the stats cache for follow/unfollow lookups
|
||||||
|
const idToUrl = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store account stats in cache.
|
* Store account stats in cache.
|
||||||
* @param {string} actorUrl - The actor's URL (cache key)
|
* @param {string} actorUrl - The actor's URL (cache key)
|
||||||
@@ -28,6 +34,10 @@ export function cacheAccountStats(actorUrl, stats) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cache.set(actorUrl, { ...stats, cachedAt: Date.now() });
|
cache.set(actorUrl, { ...stats, cachedAt: Date.now() });
|
||||||
|
|
||||||
|
// Maintain reverse lookup
|
||||||
|
const hashId = remoteActorId(actorUrl);
|
||||||
|
if (hashId) idToUrl.set(hashId, actorUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,3 +59,12 @@ export function getCachedAccountStats(actorUrl) {
|
|||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse lookup: get actor URL from account hash ID.
|
||||||
|
* @param {string} hashId - The 24-char hex account ID
|
||||||
|
* @returns {string|null} Actor URL or null
|
||||||
|
*/
|
||||||
|
export function getActorUrlFromId(hashId) {
|
||||||
|
return idToUrl.get(hashId) || null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { serializeStatus } from "../entities/status.js";
|
|||||||
import { accountId, remoteActorId } from "../helpers/id-mapping.js";
|
import { accountId, remoteActorId } from "../helpers/id-mapping.js";
|
||||||
import { buildPaginationQuery, parseLimit, setPaginationHeaders } from "../helpers/pagination.js";
|
import { buildPaginationQuery, parseLimit, setPaginationHeaders } from "../helpers/pagination.js";
|
||||||
import { resolveRemoteAccount } from "../helpers/resolve-account.js";
|
import { resolveRemoteAccount } from "../helpers/resolve-account.js";
|
||||||
|
import { getActorUrlFromId } from "../helpers/account-cache.js";
|
||||||
|
|
||||||
const router = express.Router(); // eslint-disable-line new-cap
|
const router = express.Router(); // eslint-disable-line new-cap
|
||||||
|
|
||||||
@@ -714,6 +715,10 @@ async function resolveActorUrl(id, collections) {
|
|||||||
return profile.url;
|
return profile.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check account cache reverse lookup (populated by resolveRemoteAccount)
|
||||||
|
const cachedUrl = getActorUrlFromId(id);
|
||||||
|
if (cachedUrl) return cachedUrl;
|
||||||
|
|
||||||
// Check followers
|
// Check followers
|
||||||
const followers = await collections.ap_followers.find({}).toArray();
|
const followers = await collections.ap_followers.find({}).toArray();
|
||||||
for (const f of followers) {
|
for (const f of followers) {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||||
"version": "3.7.0",
|
"version": "3.7.1",
|
||||||
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"indiekit",
|
"indiekit",
|
||||||
|
|||||||
Reference in New Issue
Block a user