mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
Replace unbounded ap_kv MongoDB collection (169K docs, 49MB) with Redis: - Fedify KV store uses @fedify/redis RedisKvStore (native TTL support) - Plugin cache (fedidb, batch-refollow state, migration flags) uses new redis-cache.js utility with indiekit: key prefix - All controllers updated to remove kvCollection parameter passing - Addresses OOM kills caused by ap_kv growing ~14K entries/day
83 lines
2.5 KiB
JavaScript
83 lines
2.5 KiB
JavaScript
/**
|
|
* Migration: separate-mentions
|
|
*
|
|
* Moves @-prefixed entries from category[] to a new mentions[] array in all
|
|
* ap_timeline documents. Tracked in ap_kv for idempotency.
|
|
*
|
|
* Before: category: ["@user@instance", "hashtag", "@another@host"]
|
|
* After: category: ["hashtag"]
|
|
* mentions: [{ name: "user@instance", url: "" }, { name: "another@host", url: "" }]
|
|
*
|
|
* Note: URLs are empty for legacy items since we can't reconstruct them.
|
|
* New items will have URLs populated by the fixed extractObjectData() (Task 1).
|
|
*/
|
|
|
|
import { cacheGet, cacheSet } from "../redis-cache.js";
|
|
|
|
const MIGRATION_KEY = "migration:separate-mentions";
|
|
|
|
/**
|
|
* Run the separate-mentions migration (idempotent)
|
|
* @param {object} collections - MongoDB collections
|
|
* @returns {Promise<{ skipped: boolean, updated: number }>}
|
|
*/
|
|
export async function runSeparateMentionsMigration(collections) {
|
|
const { ap_timeline } = collections;
|
|
|
|
// Check if already completed
|
|
const state = await cacheGet(MIGRATION_KEY);
|
|
if (state?.completed) {
|
|
return { skipped: true, updated: 0 };
|
|
}
|
|
|
|
// Find all documents where category[] contains @-prefixed entries
|
|
const docs = await ap_timeline
|
|
.find({ category: { $regex: /^@/ } })
|
|
.toArray();
|
|
|
|
if (docs.length === 0) {
|
|
// No docs to migrate — mark complete immediately
|
|
await cacheSet(MIGRATION_KEY, { completed: true, date: new Date().toISOString(), updated: 0 });
|
|
return { skipped: false, updated: 0 };
|
|
}
|
|
|
|
// Build bulk operations
|
|
const ops = docs.map((doc) => {
|
|
const mentions = (doc.mentions || []).slice(); // preserve any existing mentions
|
|
const newCategory = [];
|
|
|
|
for (const entry of doc.category || []) {
|
|
if (typeof entry === "string" && entry.startsWith("@")) {
|
|
// Move to mentions[] — strip leading @ to match timeline-store convention
|
|
const strippedName = entry.slice(1);
|
|
const alreadyPresent = mentions.some((m) => m.name === strippedName);
|
|
if (!alreadyPresent) {
|
|
mentions.push({ name: strippedName, url: "" });
|
|
}
|
|
} else {
|
|
newCategory.push(entry);
|
|
}
|
|
}
|
|
|
|
return {
|
|
updateOne: {
|
|
filter: { _id: doc._id },
|
|
update: {
|
|
$set: {
|
|
category: newCategory,
|
|
mentions
|
|
}
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
const result = await ap_timeline.bulkWrite(ops, { ordered: false });
|
|
const updated = result.modifiedCount || 0;
|
|
|
|
// Mark migration complete
|
|
await cacheSet(MIGRATION_KEY, { completed: true, date: new Date().toISOString(), updated });
|
|
|
|
return { skipped: false, updated };
|
|
}
|