This commit is contained in:
+116
@@ -339,6 +339,108 @@ function createUploadMiddleware(uploadDir) {
|
||||
return multer({ storage });
|
||||
}
|
||||
|
||||
function normalizeUploadReference(uploadPath) {
|
||||
const value = String(uploadPath || '').trim();
|
||||
if (!value || !value.startsWith('/uploads/')) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function collectUploadReferencesFromValue(value, refs) {
|
||||
if (!value) {
|
||||
return refs;
|
||||
}
|
||||
const stack = [value];
|
||||
while (stack.length) {
|
||||
const current = stack.pop();
|
||||
if (Array.isArray(current)) {
|
||||
current.forEach(function (item) {
|
||||
stack.push(item);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (current && typeof current === 'object') {
|
||||
Object.keys(current).forEach(function (key) {
|
||||
stack.push(current[key]);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (typeof current === 'string') {
|
||||
const reference = normalizeUploadReference(current);
|
||||
if (reference) {
|
||||
refs.add(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
function collectUploadReferencesFromSlide(slide) {
|
||||
const refs = new Set();
|
||||
if (!slide) {
|
||||
return refs;
|
||||
}
|
||||
collectUploadReferencesFromValue(slide.media_path, refs);
|
||||
collectUploadReferencesFromValue(common.parseJsonSafe(slide.content_json), refs);
|
||||
return refs;
|
||||
}
|
||||
|
||||
function collectUploadReferencesFromTemplate(template) {
|
||||
const refs = new Set();
|
||||
if (!template) {
|
||||
return refs;
|
||||
}
|
||||
collectUploadReferencesFromValue(template.background_image_path, refs);
|
||||
return refs;
|
||||
}
|
||||
|
||||
function collectUploadReferencesFromPayload(payload) {
|
||||
const refs = new Set();
|
||||
if (!payload) {
|
||||
return refs;
|
||||
}
|
||||
collectUploadReferencesFromValue(payload.mediaPath, refs);
|
||||
collectUploadReferencesFromValue(common.parseJsonSafe(payload.contentJson), refs);
|
||||
collectUploadReferencesFromValue(payload.backgroundImagePath, refs);
|
||||
return refs;
|
||||
}
|
||||
|
||||
async function countUploadReferences(pool, uploadPath) {
|
||||
const [slideRows] = await pool.query(
|
||||
`SELECT COUNT(*) AS ref_count
|
||||
FROM slides
|
||||
WHERE media_path = ?
|
||||
OR JSON_SEARCH(COALESCE(content_json, JSON_OBJECT()), 'one', ?) IS NOT NULL`,
|
||||
[uploadPath, uploadPath]
|
||||
);
|
||||
const [templateRows] = await pool.query(
|
||||
'SELECT COUNT(*) AS ref_count FROM slide_templates WHERE background_image_path = ?',
|
||||
[uploadPath]
|
||||
);
|
||||
return Number(slideRows[0].ref_count || 0) + Number(templateRows[0].ref_count || 0);
|
||||
}
|
||||
|
||||
async function removeUnusedUploadFiles(pool, uploadDir, uploadPaths) {
|
||||
const uniquePaths = Array.from(new Set((uploadPaths || []).map(normalizeUploadReference).filter(Boolean)));
|
||||
for (let i = 0; i < uniquePaths.length; i += 1) {
|
||||
const uploadPath = uniquePaths[i];
|
||||
const referenceCount = await countUploadReferences(pool, uploadPath);
|
||||
if (referenceCount > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadDir, path.basename(uploadPath));
|
||||
try {
|
||||
await fs.promises.unlink(filePath);
|
||||
} catch (error) {
|
||||
if (error && error.code !== 'ENOENT') {
|
||||
console.warn('Unable to remove unused upload file:', filePath, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseCookies(cookieHeader) {
|
||||
return String(cookieHeader || '').split(/;\s*/).reduce(function (cookies, pair) {
|
||||
if (!pair) {
|
||||
@@ -1448,12 +1550,17 @@ async function start() {
|
||||
return res.status(404).send('Slide not found');
|
||||
}
|
||||
const affectedScreens = await fetchScreensBySlideId(pool, slide.id);
|
||||
const existingUploadRefs = collectUploadReferencesFromSlide(slide);
|
||||
const payload = await common.buildSlidePayload(pool, req, slide);
|
||||
const nextUploadRefs = collectUploadReferencesFromPayload(payload);
|
||||
const actorId = getAuditUserId(req);
|
||||
await pool.query(
|
||||
'UPDATE slides SET title = ?, body = ?, template_id = ?, content_json = ?, media_path = ?, media_type = ?, modified_by = ? WHERE id = ?',
|
||||
[payload.title, payload.body, payload.templateId, payload.contentJson, payload.mediaPath, payload.mediaType, actorId, slide.id]
|
||||
);
|
||||
await removeUnusedUploadFiles(pool, UPLOAD_DIR, Array.from(existingUploadRefs).filter(function (reference) {
|
||||
return !nextUploadRefs.has(reference);
|
||||
}));
|
||||
await notifyPlayerScreens(affectedScreens, 'refresh');
|
||||
await broadcastDashboardState();
|
||||
res.redirect('/admin/slides/' + slide.id + '/edit?message=' + encodeURIComponent('Slide updated.'));
|
||||
@@ -1469,7 +1576,9 @@ async function start() {
|
||||
return res.status(404).send('Slide not found');
|
||||
}
|
||||
const affectedScreens = await fetchScreensBySlideId(pool, slide.id);
|
||||
const uploadRefs = collectUploadReferencesFromSlide(slide);
|
||||
await pool.query('DELETE FROM slides WHERE id = ?', [slide.id]);
|
||||
await removeUnusedUploadFiles(pool, UPLOAD_DIR, Array.from(uploadRefs));
|
||||
await notifyPlayerScreens(affectedScreens, 'refresh');
|
||||
await broadcastDashboardState();
|
||||
res.redirect('/admin/slides?message=' + encodeURIComponent('Slide deleted.'));
|
||||
@@ -1536,12 +1645,17 @@ async function start() {
|
||||
if (!template) {
|
||||
return res.status(404).send('Template not found');
|
||||
}
|
||||
const existingUploadRefs = collectUploadReferencesFromTemplate(template);
|
||||
const payload = await common.buildTemplatePayload(pool, req, template);
|
||||
const nextUploadRefs = collectUploadReferencesFromPayload(payload);
|
||||
const actorId = getAuditUserId(req);
|
||||
await pool.query(
|
||||
'UPDATE slide_templates SET name = ?, canvas_size_id = ?, background_image_path = ?, modified_by = ? WHERE id = ?',
|
||||
[payload.name, payload.canvasSizeId, payload.backgroundImagePath, actorId, template.id]
|
||||
);
|
||||
await removeUnusedUploadFiles(pool, UPLOAD_DIR, Array.from(existingUploadRefs).filter(function (reference) {
|
||||
return !nextUploadRefs.has(reference);
|
||||
}));
|
||||
await pool.query('DELETE FROM slide_template_regions WHERE template_id = ?', [template.id]);
|
||||
for (let i = 0; i < payload.regions.length; i += 1) {
|
||||
const region = payload.regions[i];
|
||||
@@ -1562,9 +1676,11 @@ async function start() {
|
||||
if (!template) {
|
||||
return res.status(404).send('Template not found');
|
||||
}
|
||||
const uploadRefs = collectUploadReferencesFromTemplate(template);
|
||||
await pool.query('UPDATE slides SET template_id = NULL, modified_by = ? WHERE template_id = ?', [getAuditUserId(req), template.id]);
|
||||
await pool.query('DELETE FROM slide_template_regions WHERE template_id = ?', [template.id]);
|
||||
await pool.query('DELETE FROM slide_templates WHERE id = ?', [template.id]);
|
||||
await removeUnusedUploadFiles(pool, UPLOAD_DIR, Array.from(uploadRefs));
|
||||
res.redirect('/admin/templates?message=' + encodeURIComponent('Template deleted.'));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
Reference in New Issue
Block a user