mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
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 ────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
const { actor, actorUrl } = await resolveActorData(id, collections);
|
||||
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 });
|
||||
// Count this actor's posts in our timeline
|
||||
account.statuses_count = await collections.ap_timeline.countDocuments({
|
||||
"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 ───────────────────────────────────────
|
||||
|
||||
router.post("/api/v1/accounts/:id/follow", async (req, res, next) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
|
||||
Reference in New Issue
Block a user