feat: add startup cleanup for old read items

Runs cleanupAllReadItems on server startup to clean up accumulated
read items from all channels, keeping only the 30 most recent per
channel per user.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-07 00:52:29 +01:00
parent 722a884ca3
commit 6caf37a003
3 changed files with 69 additions and 2 deletions

View File

@@ -6,7 +6,7 @@ import { microsubController } from "./lib/controllers/microsub.js";
import { readerController } from "./lib/controllers/reader.js";
import { handleMediaProxy } from "./lib/media/proxy.js";
import { startScheduler, stopScheduler } from "./lib/polling/scheduler.js";
import { createIndexes } from "./lib/storage/items.js";
import { cleanupAllReadItems, createIndexes } from "./lib/storage/items.js";
import { webmentionReceiver } from "./lib/webmention/receiver.js";
import { websubHandler } from "./lib/websub/handler.js";
@@ -157,6 +157,11 @@ export default class MicrosubEndpoint {
createIndexes(indiekit).catch((error) => {
console.warn("[Microsub] Index creation failed:", error.message);
});
// Cleanup old read items on startup
cleanupAllReadItems(indiekit).catch((error) => {
console.warn("[Microsub] Startup cleanup failed:", error.message);
});
} else {
console.warn(
"[Microsub] Database not available at init, scheduler not started",

View File

@@ -328,6 +328,68 @@ async function cleanupOldReadItems(collection, channelObjectId, userId) {
}
}
/**
* Cleanup all read items across all channels (startup cleanup)
* @param {object} application - Indiekit application
* @returns {Promise<number>} Total number of items deleted
*/
export async function cleanupAllReadItems(application) {
const collection = getCollection(application);
const channelsCollection = application.collections.get("microsub_channels");
// Get all channels
const channels = await channelsCollection.find({}).toArray();
let totalDeleted = 0;
for (const channel of channels) {
// Get unique userIds who have read items in this channel
const readByUsers = await collection.distinct("readBy", {
channelId: channel._id,
readBy: { $exists: true, $ne: [] },
});
for (const userId of readByUsers) {
if (!userId) continue;
const readCount = await collection.countDocuments({
channelId: channel._id,
readBy: userId,
});
if (readCount > MAX_READ_ITEMS) {
const itemsToDelete = await collection
.find({
channelId: channel._id,
readBy: userId,
})
.sort({ published: -1, _id: -1 })
.skip(MAX_READ_ITEMS)
.project({ _id: 1 })
.toArray();
if (itemsToDelete.length > 0) {
const idsToDelete = itemsToDelete.map((item) => item._id);
const deleteResult = await collection.deleteMany({
_id: { $in: idsToDelete },
});
totalDeleted += deleteResult.deletedCount;
console.info(
`[Microsub] Startup cleanup: deleted ${deleteResult.deletedCount} old items from channel "${channel.name}"`,
);
}
}
}
}
if (totalDeleted > 0) {
console.info(
`[Microsub] Startup cleanup complete: ${totalDeleted} total items deleted`,
);
}
return totalDeleted;
}
export async function markItemsRead(application, channelId, entryIds, userId) {
const collection = getCollection(application);
const channelObjectId =

View File

@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-microsub",
"version": "1.0.21",
"version": "1.0.22",
"description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.",
"keywords": [
"indiekit",