diff --git a/public/app.mjs b/public/app.mjs
index 19e7471..4ee9202 100644
--- a/public/app.mjs
+++ b/public/app.mjs
@@ -2509,6 +2509,25 @@ async function init() {
}
});
+ document.getElementById('maint-purge-sources').addEventListener('click', async () => {
+ maint_dropdown.hidden = true;
+ maint_toggle.textContent = '⏳';
+ maint_toggle.disabled = true;
+ try {
+ const result = await api.maintenance_purge_missing_sources();
+ if (result.removed.length > 0) {
+ all_sources = all_sources.filter(s => !result.removed.includes(s.id));
+ render();
+ }
+ alert(`Removed ${result.removed.length} orphaned entr${result.removed.length === 1 ? 'y' : 'ies'} of ${result.total} checked.`);
+ } catch (err) {
+ alert(`Error: ${err.message}`);
+ } finally {
+ maint_toggle.textContent = '⚙';
+ maint_toggle.disabled = false;
+ }
+ });
+
window.addEventListener('popstate', () => { parse_url(); render(); });
await load_all();
diff --git a/public/index.html b/public/index.html
index 522f603..b2462ae 100644
--- a/public/index.html
+++ b/public/index.html
@@ -23,6 +23,7 @@
+
diff --git a/public/lib/api.mjs b/public/lib/api.mjs
index edad102..458ef4d 100644
--- a/public/lib/api.mjs
+++ b/public/lib/api.mjs
@@ -86,7 +86,8 @@ export async function upload_bin(file, name) {
}
// Maintenance
-export const maintenance_pdf_thumbs = () => req('POST', '/api/maintenance/pdf-thumbs');
+export const maintenance_pdf_thumbs = () => req('POST', '/api/maintenance/pdf-thumbs');
+export const maintenance_purge_missing_sources = () => req('POST', '/api/maintenance/purge-missing-sources');
// Grid images
export const get_grids = () => req('GET', '/api/grid-images');
diff --git a/server.mjs b/server.mjs
index bf428a0..ce30114 100644
--- a/server.mjs
+++ b/server.mjs
@@ -3,7 +3,7 @@ process.on('uncaughtException', (err) => { console.error('[uncaughtException
import express from 'express';
import multer from 'multer';
-import { unlinkSync, mkdirSync } from 'node:fs';
+import { unlinkSync, mkdirSync, existsSync } from 'node:fs';
import { extname, join } from 'node:path';
import { execFileSync } from 'node:child_process';
import sharp from 'sharp';
@@ -634,6 +634,18 @@ app.delete('/api/pdfs/:id', (req, res) => {
// Maintenance
// ---------------------------------------------------------------------------
+app.post('/api/maintenance/purge-missing-sources', (req, res) => {
+ const sources = list_source_images();
+ const removed = [];
+ for (const src of sources) {
+ if (!existsSync(join('./data/images', src.id))) {
+ delete_source_image(src.id);
+ removed.push(src.id);
+ }
+ }
+ ok(res, { removed, total: sources.length });
+});
+
app.post('/api/maintenance/pdf-thumbs', (req, res) => {
const pdfs = list_pdfs();
let generated = 0;