From 441401221d920f9d0bec39cfcea4589fd75745f3 Mon Sep 17 00:00:00 2001 From: Hudson <258693705+hudson-rivera@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:23:02 -0500 Subject: [PATCH] fix(media): clean expired files in subdirectories cleanOldMedia() only scanned the top-level media directory, but saveMediaBuffer() writes to subdirs (inbound/, outbound/, browser/). Files in those subdirs were never cleaned up. Now recurses one level into subdirectories, deleting expired files while preserving the subdirectory folders themselves. --- src/media/store.test.ts | 15 +++++++++++++++ src/media/store.ts | 22 +++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/media/store.test.ts b/src/media/store.test.ts index 0b6313c6b..8c611550d 100644 --- a/src/media/store.test.ts +++ b/src/media/store.test.ts @@ -102,6 +102,21 @@ describe("media store", () => { }); }); + it("cleans old media files in first-level subdirectories", async () => { + await withTempStore(async (store) => { + const saved = await store.saveMediaBuffer(Buffer.from("nested"), "text/plain", "inbound"); + const inboundDir = path.dirname(saved.path); + const past = Date.now() - 10_000; + await fs.utimes(saved.path, past / 1000, past / 1000); + + await store.cleanOldMedia(1); + + await expect(fs.stat(saved.path)).rejects.toThrow(); + const inboundStat = await fs.stat(inboundDir); + expect(inboundStat.isDirectory()).toBe(true); + }); + }); + it("sets correct mime for xlsx by extension", async () => { await withTempStore(async (store, home) => { const xlsxPath = path.join(home, "sheet.xlsx"); diff --git a/src/media/store.ts b/src/media/store.ts index dafbf2bbc..c5882ae10 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -88,6 +88,22 @@ export async function cleanOldMedia(ttlMs = DEFAULT_TTL_MS) { const mediaDir = await ensureMediaDir(); const entries = await fs.readdir(mediaDir).catch(() => []); const now = Date.now(); + const removeExpiredFilesInDir = async (dir: string) => { + const dirEntries = await fs.readdir(dir).catch(() => []); + await Promise.all( + dirEntries.map(async (entry) => { + const full = path.join(dir, entry); + const stat = await fs.stat(full).catch(() => null); + if (!stat || !stat.isFile()) { + return; + } + if (now - stat.mtimeMs > ttlMs) { + await fs.rm(full).catch(() => {}); + } + }), + ); + }; + await Promise.all( entries.map(async (file) => { const full = path.join(mediaDir, file); @@ -95,7 +111,11 @@ export async function cleanOldMedia(ttlMs = DEFAULT_TTL_MS) { if (!stat) { return; } - if (now - stat.mtimeMs > ttlMs) { + if (stat.isDirectory()) { + await removeExpiredFilesInDir(full); + return; + } + if (stat.isFile() && now - stat.mtimeMs > ttlMs) { await fs.rm(full).catch(() => {}); } }),