fix: exclude soft-deleted posts from outbox and content negotiation

Deleted posts (with properties.deleted timestamp) were still served
via the outbox dispatcher and content negotiation catch-all. Now:
- Outbox find() and countDocuments() filter out deleted posts
- Object dispatcher returns null for deleted posts (Fedify 404)
- Content negotiation falls through to Express for deleted posts

Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
This commit is contained in:
Ricardo
2026-03-17 17:12:30 +01:00
parent d676374ec1
commit 26c81a6a76
2 changed files with 9 additions and 4 deletions

View File

@@ -434,7 +434,7 @@ export default class ActivityPubEndpoint {
"properties.url": requestUrl,
});
if (!post) {
if (!post || post.properties?.deleted) {
return next();
}

View File

@@ -609,10 +609,11 @@ function setupOutbox(federation, mountPath, handle, collections) {
const pageSize = 20;
const skip = cursor ? Number.parseInt(cursor, 10) : 0;
const total = await postsCollection.countDocuments();
const notDeleted = { "properties.deleted": { $exists: false } };
const total = await postsCollection.countDocuments(notDeleted);
const posts = await postsCollection
.find()
.find(notDeleted)
.sort({ "properties.published": -1 })
.skip(skip)
.limit(pageSize)
@@ -644,7 +645,9 @@ function setupOutbox(federation, mountPath, handle, collections) {
if (identifier !== handle) return 0;
const postsCollection = collections.posts;
if (!postsCollection) return 0;
return await postsCollection.countDocuments();
return await postsCollection.countDocuments({
"properties.deleted": { $exists: false },
});
})
.setFirstCursor(async () => "0");
}
@@ -656,6 +659,8 @@ function setupObjectDispatchers(federation, mountPath, handle, collections, publ
const postUrl = `${publicationUrl.replace(/\/$/, "")}/${id}`;
const post = await collections.posts.findOne({ "properties.url": postUrl });
if (!post) return null;
// Soft-deleted posts should not be dereferenceable
if (post.properties?.deleted) return null;
const actorUrl = ctx.getActorUri(handle).href;
const activity = jf2ToAS2Activity(post.properties, actorUrl, publicationUrl);
// Only Create activities wrap Note/Article objects