Add maintenance: purge orphaned source image KV entries
When a source image file is deleted without going through the API (e.g. the old bin delete bug), the KV entry remains and shows a broken image. The new maintenance action scans all source image entries, removes any whose file is missing on disk, and reports how many were cleaned up. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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(); });
|
window.addEventListener('popstate', () => { parse_url(); render(); });
|
||||||
|
|
||||||
await load_all();
|
await load_all();
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<button class="maint-toggle" id="maint-toggle" title="Maintenance">⚙</button>
|
<button class="maint-toggle" id="maint-toggle" title="Maintenance">⚙</button>
|
||||||
<div class="maint-dropdown" id="maint-dropdown" hidden>
|
<div class="maint-dropdown" id="maint-dropdown" hidden>
|
||||||
<button class="maint-item" id="maint-gen-thumbs">Generate missing PDF thumbnails</button>
|
<button class="maint-item" id="maint-gen-thumbs">Generate missing PDF thumbnails</button>
|
||||||
|
<button class="maint-item" id="maint-purge-sources">Remove orphaned source image entries</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export async function upload_bin(file, name) {
|
|||||||
|
|
||||||
// Maintenance
|
// 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
|
// Grid images
|
||||||
export const get_grids = () => req('GET', '/api/grid-images');
|
export const get_grids = () => req('GET', '/api/grid-images');
|
||||||
|
|||||||
14
server.mjs
14
server.mjs
@@ -3,7 +3,7 @@ process.on('uncaughtException', (err) => { console.error('[uncaughtException
|
|||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
import { unlinkSync, mkdirSync } from 'node:fs';
|
import { unlinkSync, mkdirSync, existsSync } from 'node:fs';
|
||||||
import { extname, join } from 'node:path';
|
import { extname, join } from 'node:path';
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
@@ -634,6 +634,18 @@ app.delete('/api/pdfs/:id', (req, res) => {
|
|||||||
// Maintenance
|
// 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) => {
|
app.post('/api/maintenance/pdf-thumbs', (req, res) => {
|
||||||
const pdfs = list_pdfs();
|
const pdfs = list_pdfs();
|
||||||
let generated = 0;
|
let generated = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user