feat: federation management page with collection stats, post actions, object lookup (v2.12.0)

Confab-Link: http://localhost:8080/sessions/c2335791-4b8c-44a6-b1b7-8d0fa8d7f647
This commit is contained in:
Ricardo
2026-03-15 16:32:14 +01:00
parent 6238e7d4e5
commit 19aa83ab8d
7 changed files with 852 additions and 5 deletions

View File

@@ -99,6 +99,13 @@ import { logActivity } from "./lib/activity-log.js";
import { scheduleCleanup } from "./lib/timeline-cleanup.js";
import { runSeparateMentionsMigration } from "./lib/migrations/separate-mentions.js";
import { deleteFederationController } from "./lib/controllers/federation-delete.js";
import {
federationMgmtController,
rebroadcastController,
viewApJsonController,
broadcastActorUpdateController,
lookupObjectController,
} from "./lib/controllers/federation-mgmt.js";
const defaults = {
mountPath: "/activitypub",
@@ -169,6 +176,11 @@ export default class ActivityPubEndpoint {
text: "activitypub.myProfile.title",
requiresDatabase: true,
},
{
href: `${this.options.mountPath}/admin/federation`,
text: "activitypub.federationMgmt.title",
requiresDatabase: true,
},
];
}
@@ -313,6 +325,11 @@ export default class ActivityPubEndpoint {
router.post("/admin/refollow/resume", refollowResumeController(mp, this));
router.get("/admin/refollow/status", refollowStatusController(mp));
router.post("/admin/federation/delete", deleteFederationController(mp, this));
router.get("/admin/federation", federationMgmtController(mp, this));
router.post("/admin/federation/rebroadcast", rebroadcastController(mp, this));
router.get("/admin/federation/ap-json", viewApJsonController(mp, this));
router.post("/admin/federation/broadcast-actor", broadcastActorUpdateController(mp, this));
router.get("/admin/federation/lookup", lookupObjectController(mp, this));
return router;
}
@@ -326,6 +343,38 @@ export default class ActivityPubEndpoint {
const router = express.Router(); // eslint-disable-line new-cap
const self = this;
// Intercept Micropub delete actions to broadcast Delete to fediverse.
// Wraps res.json to detect successful delete responses, then fires
// broadcastDelete asynchronously so remote servers remove the post.
router.use((req, res, next) => {
if (req.method !== "POST") return next();
if (!req.path.endsWith("/micropub")) return next();
const action = req.query?.action || req.body?.action;
if (action !== "delete") return next();
const postUrl = req.query?.url || req.body?.url;
if (!postUrl) return next();
const originalJson = res.json.bind(res);
res.json = function (body) {
// Fire broadcastDelete after successful delete (status 200)
if (res.statusCode === 200 && body?.success === "delete") {
console.info(
`[ActivityPub] Micropub delete detected for ${postUrl}, broadcasting Delete to followers`,
);
self.broadcastDelete(postUrl).catch((error) => {
console.warn(
`[ActivityPub] broadcastDelete after Micropub delete failed: ${error.message}`,
);
});
}
return originalJson(body);
};
return next();
});
// Let Fedify handle NodeInfo data (/nodeinfo/2.1)
// Only pass GET/HEAD requests — POST/PUT/DELETE must not go through
// Fedify here, because fromExpressRequest() consumes the body stream,
@@ -483,6 +532,7 @@ export default class ActivityPubEndpoint {
resolvedMentions.push({
handle,
actorUrl: mentionedActor.id.href,
profileUrl: mentionedActor.url?.href || null,
});
mentionRecipients.push({
handle,