fix: Interlinking without proper Post Title
Deploy Indiekit Server / deploy (push) Successful in 1m21s

This commit is contained in:
Sven
2026-04-10 10:06:54 +02:00
parent 1032a33d65
commit 5e2e08c3a2
@@ -0,0 +1,112 @@
/**
* Patch: preserve liked/bookmarked/boosted items during timeline cleanup.
*
* Root cause:
* cleanupTimeline() removes old remote posts from ap_timeline and also
* deletes their ap_interactions entries. Any post the user has liked,
* bookmarked, or boosted disappears from GET /api/v1/favourites and
* GET /api/v1/bookmarks after the next daily cleanup run.
*
* Fix:
* Before deleting, fetch the set of objectUrls that have ap_interactions
* entries (likes, bookmarks, boosts). Filter those out of the deletion
* candidate list so they are preserved in ap_timeline (and their
* ap_interactions entries are never touched).
*/
import { access, readFile, writeFile } from "node:fs/promises";
const MARKER = "// [patch] ap-interactions-cleanup-preserve";
const candidates = [
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/timeline-cleanup.js",
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/timeline-cleanup.js",
];
const OLD_SNIPPET = ` const removedUids = toDelete.map((item) => item.uid).filter(Boolean);
// Delete old timeline items by UID
const deleteResult = await collections.ap_timeline.deleteMany({
_id: { $in: toDelete.map((item) => item._id) },
});
// Clean up stale interactions for removed items
let interactionsRemoved = 0;
if (removedUids.length > 0 && collections.ap_interactions) {
const interactionResult = await collections.ap_interactions.deleteMany({
objectUrl: { $in: removedUids },
});
interactionsRemoved = interactionResult.deletedCount || 0;
}`;
const NEW_SNIPPET = ` const removedUids = toDelete.map((item) => item.uid).filter(Boolean);
// Preserve items the user has interacted with (liked, bookmarked, boosted). ${MARKER}
// Deleting them would silently remove entries from the Favourites/Bookmarks pages.
let interactedUids = new Set();
if (removedUids.length > 0 && collections.ap_interactions) {
const interacted = await collections.ap_interactions.distinct("objectUrl");
interactedUids = new Set(interacted);
}
const itemsToDelete = toDelete.filter((item) => !interactedUids.has(item.uid));
const uidsToDelete = itemsToDelete.map((item) => item.uid).filter(Boolean);
if (!itemsToDelete.length) {
return { removed: 0, interactionsRemoved: 0 };
}
// Delete old timeline items by UID
const deleteResult = await collections.ap_timeline.deleteMany({
_id: { $in: itemsToDelete.map((item) => item._id) },
});
// Clean up stale interactions for removed items
let interactionsRemoved = 0;
if (uidsToDelete.length > 0 && collections.ap_interactions) {
const interactionResult = await collections.ap_interactions.deleteMany({
objectUrl: { $in: uidsToDelete },
});
interactionsRemoved = interactionResult.deletedCount || 0;
}`;
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
let checked = 0;
let patched = 0;
for (const filePath of candidates) {
if (!(await exists(filePath))) continue;
checked += 1;
const source = await readFile(filePath, "utf8");
if (source.includes(MARKER)) {
console.log(`[postinstall] patch-ap-interactions-cleanup-preserve: already applied to ${filePath}`);
continue;
}
if (!source.includes(OLD_SNIPPET)) {
console.warn(`[postinstall] patch-ap-interactions-cleanup-preserve: snippet not found in ${filePath}`);
continue;
}
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
await writeFile(filePath, updated, "utf8");
patched += 1;
console.log(`[postinstall] Applied patch-ap-interactions-cleanup-preserve to ${filePath}`);
}
if (checked === 0) {
console.log("[postinstall] patch-ap-interactions-cleanup-preserve: no target files found");
} else if (patched === 0) {
console.log("[postinstall] patch-ap-interactions-cleanup-preserve: already up to date");
} else {
console.log(`[postinstall] patch-ap-interactions-cleanup-preserve: patched ${patched} file(s)`);
}