feat: archive aged attachment cache

This commit is contained in:
2026-07-19 05:59:41 +08:00
parent a23bc72dd3
commit 9caf598fcb
6 changed files with 14 additions and 8 deletions
+5 -1
View File
@@ -8,7 +8,7 @@ import { recordError } from "../lib/observability";
import { fetchRss } from "../lib/rss";
import { sendAlert } from "../lib/alerts";
type Source = { id: number; user_id: number; base_url: string; token_encrypted: string; remote_user: string | null; integration_type: "memos" | "rss"; rss_feed_url: string | null; is_enabled: number; sync_tags_json: string; sync_from: string | null; sync_to: string | null; sync_attachment_mode: "all" | "images" | "none"; attachment_storage_mode: "remote" | "images" | "all"; attachment_cache_limit_bytes: number; sync_batch_size: number; sync_max_posts: number | null; sync_cursor: string | null; sync_imported_count: number; sync_run_id: string | null };
type Source = { id: number; user_id: number; base_url: string; token_encrypted: string; remote_user: string | null; integration_type: "memos" | "rss"; rss_feed_url: string | null; is_enabled: number; sync_tags_json: string; sync_from: string | null; sync_to: string | null; sync_attachment_mode: "all" | "images" | "none"; attachment_storage_mode: "remote" | "images" | "all"; attachment_cache_limit_bytes: number; attachment_archive_after_days: number | null; sync_batch_size: number; sync_max_posts: number | null; sync_cursor: string | null; sync_imported_count: number; sync_run_id: string | null };
type Job = { id: number; source_id: number; kind: "pull" | "push"; payload_json: string | null; attempts: number };
function remoteAttachmentUrl(attachment: any, baseUrl: string) {
@@ -39,6 +39,10 @@ async function cacheAttachments(source: Source, attachments: any[]) {
}
async function cleanupCache(source: Source) {
if (source.attachment_archive_after_days) {
const oldPosts = db.prepare("SELECT id,attachments_json FROM posts WHERE source_id=? AND COALESCE(remote_created_at,created_at)<datetime('now', ?) AND attachments_json LIKE ?").all(source.id, `-${source.attachment_archive_after_days} days`, `%/uploads/cache/source-${source.id}/%`) as { id: number; attachments_json: string }[];
for (const post of oldPosts) { try { const attachments = (JSON.parse(post.attachments_json) as any[]).map((item) => String(item.url || "").startsWith(`/uploads/cache/source-${source.id}/`) && item.originalUrl ? { ...item, url: item.originalUrl } : item); db.prepare("UPDATE posts SET attachments_json=?,updated_at=CURRENT_TIMESTAMP WHERE id=?").run(JSON.stringify(attachments), post.id); } catch {} }
}
const directory = join(process.cwd(), "public", "uploads", "cache", `source-${source.id}`); let names: string[]; try { names = await readdir(directory); } catch { return; }
const used = new Set<string>(); for (const row of db.prepare("SELECT attachments_json FROM posts WHERE source_id=?").all(source.id) as { attachments_json: string }[]) { try { for (const attachment of JSON.parse(row.attachments_json) as any[]) { const url = String(attachment.url || ""); if (url.startsWith(`/uploads/cache/source-${source.id}/`)) used.add(url.split("/").at(-1)!); } } catch {} }
await Promise.all(names.filter((name) => !used.has(name)).map((name) => unlink(join(directory, name)).catch(() => undefined)));