fix: route ordering + remote resolution for account profiles
Two bugs causing profile counts to show 0 in Phanpy: 1. Route ordering: /accounts/relationships and /accounts/familiar_followers were defined AFTER /accounts/:id. Express matched "relationships" as the :id parameter, returning 404. Moved them before the :id catch-all. 2. /accounts/:id only used local data (followers/following/timeline) which has no follower counts. Now tries remote actor resolution via Fedify to get real counts from AP collection totalItems.
This commit is contained in:
@@ -155,6 +155,65 @@ router.get("/api/v1/accounts/lookup", async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── GET /api/v1/accounts/relationships ──────────────────────────────────────
|
||||||
|
// MUST be before /accounts/:id to prevent Express matching "relationships" as :id
|
||||||
|
|
||||||
|
router.get("/api/v1/accounts/relationships", async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
let ids = req.query["id[]"] || req.query.id || [];
|
||||||
|
if (!Array.isArray(ids)) ids = [ids];
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return res.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const collections = req.app.locals.mastodonCollections;
|
||||||
|
|
||||||
|
const [followers, following, blocked, muted] = await Promise.all([
|
||||||
|
collections.ap_followers.find({}).toArray(),
|
||||||
|
collections.ap_following.find({}).toArray(),
|
||||||
|
collections.ap_blocked.find({}).toArray(),
|
||||||
|
collections.ap_muted.find({}).toArray(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const followerIds = new Set(followers.map((f) => remoteActorId(f.actorUrl)));
|
||||||
|
const followingIds = new Set(following.map((f) => remoteActorId(f.actorUrl)));
|
||||||
|
const blockedIds = new Set(blocked.map((b) => remoteActorId(b.url)));
|
||||||
|
const mutedIds = new Set(muted.filter((m) => m.url).map((m) => remoteActorId(m.url)));
|
||||||
|
|
||||||
|
const relationships = ids.map((id) => ({
|
||||||
|
id,
|
||||||
|
following: followingIds.has(id),
|
||||||
|
showing_reblogs: followingIds.has(id),
|
||||||
|
notifying: false,
|
||||||
|
languages: [],
|
||||||
|
followed_by: followerIds.has(id),
|
||||||
|
blocking: blockedIds.has(id),
|
||||||
|
blocked_by: false,
|
||||||
|
muting: mutedIds.has(id),
|
||||||
|
muting_notifications: mutedIds.has(id),
|
||||||
|
requested: false,
|
||||||
|
requested_by: false,
|
||||||
|
domain_blocking: false,
|
||||||
|
endorsed: false,
|
||||||
|
note: "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
res.json(relationships);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── GET /api/v1/accounts/familiar_followers ─────────────────────────────────
|
||||||
|
// MUST be before /accounts/:id
|
||||||
|
|
||||||
|
router.get("/api/v1/accounts/familiar_followers", (req, res) => {
|
||||||
|
let ids = req.query["id[]"] || req.query.id || [];
|
||||||
|
if (!Array.isArray(ids)) ids = [ids];
|
||||||
|
res.json(ids.map((id) => ({ id, accounts: [] })));
|
||||||
|
});
|
||||||
|
|
||||||
// ─── GET /api/v1/accounts/:id ────────────────────────────────────────────────
|
// ─── GET /api/v1/accounts/:id ────────────────────────────────────────────────
|
||||||
|
|
||||||
router.get("/api/v1/accounts/:id", async (req, res, next) => {
|
router.get("/api/v1/accounts/:id", async (req, res, next) => {
|
||||||
@@ -183,8 +242,18 @@ router.get("/api/v1/accounts/:id", async (req, res, next) => {
|
|||||||
// Resolve remote actor from followers, following, or timeline
|
// Resolve remote actor from followers, following, or timeline
|
||||||
const { actor, actorUrl } = await resolveActorData(id, collections);
|
const { actor, actorUrl } = await resolveActorData(id, collections);
|
||||||
if (actor) {
|
if (actor) {
|
||||||
|
// Try remote resolution to get real counts (followers, following, statuses)
|
||||||
|
const remoteAccount = await resolveRemoteAccount(
|
||||||
|
actorUrl,
|
||||||
|
pluginOptions,
|
||||||
|
baseUrl,
|
||||||
|
);
|
||||||
|
if (remoteAccount) {
|
||||||
|
return res.json(remoteAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to local data
|
||||||
const account = serializeAccount(actor, { baseUrl });
|
const account = serializeAccount(actor, { baseUrl });
|
||||||
// Count this actor's posts in our timeline
|
|
||||||
account.statuses_count = await collections.ap_timeline.countDocuments({
|
account.statuses_count = await collections.ap_timeline.countDocuments({
|
||||||
"author.url": actorUrl,
|
"author.url": actorUrl,
|
||||||
});
|
});
|
||||||
@@ -354,66 +423,6 @@ router.get("/api/v1/accounts/:id/following", async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── GET /api/v1/accounts/relationships ──────────────────────────────────────
|
|
||||||
|
|
||||||
router.get("/api/v1/accounts/relationships", async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// id[] can come as single value or array
|
|
||||||
let ids = req.query["id[]"] || req.query.id || [];
|
|
||||||
if (!Array.isArray(ids)) ids = [ids];
|
|
||||||
|
|
||||||
if (ids.length === 0) {
|
|
||||||
return res.json([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const collections = req.app.locals.mastodonCollections;
|
|
||||||
|
|
||||||
// Load all followers/following for efficient lookup
|
|
||||||
const [followers, following, blocked, muted] = await Promise.all([
|
|
||||||
collections.ap_followers.find({}).toArray(),
|
|
||||||
collections.ap_following.find({}).toArray(),
|
|
||||||
collections.ap_blocked.find({}).toArray(),
|
|
||||||
collections.ap_muted.find({}).toArray(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const followerIds = new Set(followers.map((f) => remoteActorId(f.actorUrl)));
|
|
||||||
const followingIds = new Set(following.map((f) => remoteActorId(f.actorUrl)));
|
|
||||||
const blockedIds = new Set(blocked.map((b) => remoteActorId(b.url)));
|
|
||||||
const mutedIds = new Set(muted.filter((m) => m.url).map((m) => remoteActorId(m.url)));
|
|
||||||
|
|
||||||
const relationships = ids.map((id) => ({
|
|
||||||
id,
|
|
||||||
following: followingIds.has(id),
|
|
||||||
showing_reblogs: followingIds.has(id),
|
|
||||||
notifying: false,
|
|
||||||
languages: [],
|
|
||||||
followed_by: followerIds.has(id),
|
|
||||||
blocking: blockedIds.has(id),
|
|
||||||
blocked_by: false,
|
|
||||||
muting: mutedIds.has(id),
|
|
||||||
muting_notifications: mutedIds.has(id),
|
|
||||||
requested: false,
|
|
||||||
requested_by: false,
|
|
||||||
domain_blocking: false,
|
|
||||||
endorsed: false,
|
|
||||||
note: "",
|
|
||||||
}));
|
|
||||||
|
|
||||||
res.json(relationships);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── GET /api/v1/accounts/familiar_followers ─────────────────────────────────
|
|
||||||
|
|
||||||
router.get("/api/v1/accounts/familiar_followers", (req, res) => {
|
|
||||||
// Stub — returns empty for each requested ID
|
|
||||||
let ids = req.query["id[]"] || req.query.id || [];
|
|
||||||
if (!Array.isArray(ids)) ids = [ids];
|
|
||||||
res.json(ids.map((id) => ({ id, accounts: [] })));
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── POST /api/v1/accounts/:id/follow ───────────────────────────────────────
|
// ─── POST /api/v1/accounts/:id/follow ───────────────────────────────────────
|
||||||
|
|
||||||
router.post("/api/v1/accounts/:id/follow", async (req, res, next) => {
|
router.post("/api/v1/accounts/:id/follow", async (req, res, next) => {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rmdes/indiekit-endpoint-activitypub",
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
||||||
"version": "3.6.5",
|
"version": "3.6.6",
|
||||||
"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