fix: Interlinking without proper Post Title
Deploy Indiekit Server / deploy (push) Successful in 1m21s
Deploy Indiekit Server / deploy (push) Successful in 1m21s
This commit is contained in:
@@ -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)`);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user