From 61819f1aebf81c602c8fa29a4a45c326d7c92adb Mon Sep 17 00:00:00 2001 From: Ricardo Date: Fri, 6 Feb 2026 22:59:46 +0100 Subject: [PATCH] feat: cleanup old read items (keep last 30 per channel) Prevents database accumulation by automatically deleting older read items when marking items as read. Keeps the 30 most recent read items per channel per user to allow revisiting recently read content. Co-Authored-By: Claude Opus 4.5 --- lib/storage/items.js | 48 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/storage/items.js b/lib/storage/items.js index 7e25096..002762b 100644 --- a/lib/storage/items.js +++ b/lib/storage/items.js @@ -288,6 +288,46 @@ export async function countReadItems(application, channelId, userId) { * @param {string} userId - User ID * @returns {Promise} Number of items updated */ +// Maximum number of read items to keep per channel +const MAX_READ_ITEMS = 30; + +/** + * Cleanup old read items, keeping only the most recent MAX_READ_ITEMS + * @param {object} collection - MongoDB collection + * @param {ObjectId} channelObjectId - Channel ObjectId + * @param {string} userId - User ID + */ +async function cleanupOldReadItems(collection, channelObjectId, userId) { + // Count read items in this channel + const readCount = await collection.countDocuments({ + channelId: channelObjectId, + readBy: userId, + }); + + if (readCount > MAX_READ_ITEMS) { + // Find the oldest read items to delete + const itemsToDelete = await collection + .find({ + channelId: channelObjectId, + readBy: userId, + }) + .sort({ published: -1, _id: -1 }) // Newest first + .skip(MAX_READ_ITEMS) // Skip the ones we want to keep + .project({ _id: 1 }) + .toArray(); + + if (itemsToDelete.length > 0) { + const idsToDelete = itemsToDelete.map((item) => item._id); + const deleteResult = await collection.deleteMany({ + _id: { $in: idsToDelete }, + }); + console.info( + `[Microsub] Cleaned up ${deleteResult.deletedCount} old read items (keeping ${MAX_READ_ITEMS})`, + ); + } + } +} + export async function markItemsRead(application, channelId, entryIds, userId) { const collection = getCollection(application); const channelObjectId = @@ -309,6 +349,10 @@ export async function markItemsRead(application, channelId, entryIds, userId) { console.info( `[Microsub] Marked all items as read: ${result.modifiedCount} updated`, ); + + // Cleanup old read items, keeping only the most recent + await cleanupOldReadItems(collection, channelObjectId, userId); + return result.modifiedCount; } @@ -339,6 +383,10 @@ export async function markItemsRead(application, channelId, entryIds, userId) { console.info( `[Microsub] markItemsRead result: ${result.modifiedCount} items updated`, ); + + // Cleanup old read items, keeping only the most recent + await cleanupOldReadItems(collection, channelObjectId, userId); + return result.modifiedCount; } diff --git a/package.json b/package.json index e92e396..f3dbbfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-microsub", - "version": "1.0.19", + "version": "1.0.20", "description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.", "keywords": [ "indiekit",