This commit is contained in:
2026-08-08 10:10:27 +02:00
parent 6dd70a0fc2
commit 3179d15b42
4 changed files with 48 additions and 9 deletions

View File

@@ -12,7 +12,7 @@ import cfg from "../config.mjs";
import security from "../security.mjs";
import crypto from "crypto";
import path from "path";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getEnablePdf, setEnablePdf, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getShitpostMode } from "../settings.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getEnablePdf, setEnablePdf, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getCleanupIncludeEngaged, setCleanupIncludeEngaged, getShitpostMode } from "../settings.mjs";
export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => {
@@ -661,6 +661,7 @@ export default (router, tpl) => {
enable_cleanup: getEnableCleanup(),
cleanup_start_date: getCleanupStartDate(),
cleanup_end_date: getCleanupEndDate(),
cleanup_include_engaged: getCleanupIncludeEngaged(),
totals: await lib.countf0cks(),
tmp: null
};
@@ -674,15 +675,18 @@ export default (router, tpl) => {
try {
const cleanup_start_date = req.post.cleanup_start_date || '';
const cleanup_end_date = req.post.cleanup_end_date || '';
const cleanup_include_engaged = req.post.cleanup_include_engaged === 'true' || req.post.cleanup_include_engaged === 'on' || req.post.cleanup_include_engaged === '1';
await db`INSERT INTO site_settings (key, value) VALUES ('cleanup_start_date', ${cleanup_start_date}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
await db`INSERT INTO site_settings (key, value) VALUES ('cleanup_end_date', ${cleanup_end_date}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
await db`INSERT INTO site_settings (key, value) VALUES ('cleanup_include_engaged', ${cleanup_include_engaged ? 'true' : 'false'}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
setCleanupStartDate(cleanup_start_date);
setCleanupEndDate(cleanup_end_date);
setCleanupIncludeEngaged(cleanup_include_engaged);
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
const body = JSON.stringify({ success: true, enable_cleanup: getEnableCleanup(), cleanup_start_date: getCleanupStartDate(), cleanup_end_date: getCleanupEndDate() });
const body = JSON.stringify({ success: true, enable_cleanup: getEnableCleanup(), cleanup_start_date: getCleanupStartDate(), cleanup_end_date: getCleanupEndDate(), cleanup_include_engaged: getCleanupIncludeEngaged() });
return res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }).end(body);
}
@@ -701,17 +705,23 @@ export default (router, tpl) => {
router.post(/^\/admin\/cleanup\/run\/?$/, lib.auth, async (req, res) => {
try {
// Ensure settings are synced from DB before execution
const settings = await db`SELECT key, value FROM site_settings WHERE key IN ('enable_cleanup', 'cleanup_start_date', 'cleanup_end_date')`;
const settings = await db`SELECT key, value FROM site_settings WHERE key IN ('enable_cleanup', 'cleanup_start_date', 'cleanup_end_date', 'cleanup_include_engaged')`;
const settingsMap = Object.fromEntries(settings.map(s => [s.key, s.value]));
const isEnabled = settingsMap['enable_cleanup'] !== undefined ? settingsMap['enable_cleanup'] === 'true' : getEnableCleanup();
const startDate = settingsMap['cleanup_start_date'] || '';
const endDate = settingsMap['cleanup_end_date'] || '';
const reqEngaged = req.post?.include_engaged;
const includeEngaged = reqEngaged !== undefined
? (reqEngaged === 'true' || reqEngaged === true || reqEngaged === 'on' || reqEngaged === '1')
: (settingsMap['cleanup_include_engaged'] === 'true' || getCleanupIncludeEngaged());
// Update memory state
setEnableCleanup(isEnabled);
setCleanupStartDate(startDate);
setCleanupEndDate(endDate);
setCleanupIncludeEngaged(includeEngaged);
if (!isEnabled) {
throw new Error('Cleanup is disabled in settings.');
@@ -721,7 +731,7 @@ export default (router, tpl) => {
throw new Error('Please select both a Start Date and an End Date.');
}
console.log(`[ADMIN] Starting manual cleanup for period ${startDate} to ${endDate}...`);
console.log(`[ADMIN] Starting manual cleanup for period ${startDate} to ${endDate} (includeEngaged: ${includeEngaged})...`);
const start_stamp = ~~(new Date(startDate).getTime() / 1000);
const end_stamp = ~~(new Date(endDate).getTime() / 1000) + 86399; // Include full end day
@@ -737,7 +747,7 @@ export default (router, tpl) => {
AND i.is_pinned = false
AND i.stamp >= ${start_stamp}
AND i.stamp <= ${end_stamp}
AND (
${includeEngaged ? db`` : db`AND (
-- Case 1: Active posts with no engagement (ignoring automatic subscriptions)
(i.active = true AND i.is_deleted = false AND NOT EXISTS (SELECT 1 FROM comments WHERE item_id = i.id) AND NOT EXISTS (SELECT 1 FROM favorites WHERE item_id = i.id))
OR
@@ -746,7 +756,7 @@ export default (router, tpl) => {
OR
-- Case 3: Pending posts (not yet approved) that are old enough
(i.active = false AND i.is_deleted = false)
)
)`}
`;
const statsInfo = `(Total items: ${totalCleanable[0].c}, In range: ${withinRange[0].c})`;

View File

@@ -14,6 +14,7 @@ let enable_pdf = false;
let enable_cleanup = false;
let cleanup_start_date = '';
let cleanup_end_date = '';
let cleanup_include_engaged = false;
export const getShitpostMode = () => !!cfg.websrv.shitpost_mode;
export const setShitpostMode = (val) => {}; // No-op, strictly config-based
@@ -29,6 +30,9 @@ export const setCleanupStartDate = (val) => cleanup_start_date = val || '';
export const getCleanupEndDate = () => cleanup_end_date;
export const setCleanupEndDate = (val) => cleanup_end_date = val || '';
export const getCleanupIncludeEngaged = () => cleanup_include_engaged;
export const setCleanupIncludeEngaged = (val) => cleanup_include_engaged = !!val;
export const getEnablePdf = () => enable_pdf;
export const setEnablePdf = (val) => enable_pdf = !!val;

View File

@@ -20,7 +20,7 @@ import { handleMetaExtract } from "./meta_extract_handler.mjs";
import { handleMetaStrip } from "./meta_strip_handler.mjs";
import { handleCommentUpload, handleCommentUploadCancel } from "./comment_upload_handler.mjs";
import { handleDmAttachmentUpload, handleDmAttachmentDownload, handleDmAttachmentDelete } from "./dm_attachment_handler.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode, getAllowCommentDeletion, setAllowCommentDeletion, getNsfpIds, setNsfpIds } from "./inc/settings.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDmAttachments, setDmAttachments, getDmUnencrypted, setDmUnencrypted, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf, getEnableCleanup, setEnableCleanup, getCleanupStartDate, setCleanupStartDate, getCleanupEndDate, setCleanupEndDate, getCleanupIncludeEngaged, setCleanupIncludeEngaged, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps, getShitpostMode, setShitpostMode, getAllowCommentDeletion, setAllowCommentDeletion, getNsfpIds, setNsfpIds } from "./inc/settings.mjs";
import { updateHallsCache, getHalls } from "./inc/halls_cache.mjs";
import { createI18n } from "./inc/i18n.mjs";
import security from "./inc/security.mjs";
@@ -1239,6 +1239,12 @@ process.on('uncaughtException', err => {
setCleanupEndDate(endSetting[0].value);
}
console.log(`[BOOT] Cleanup End Date: ${getCleanupEndDate()}`);
const engagedSetting = await db`SELECT value FROM site_settings WHERE key = 'cleanup_include_engaged' LIMIT 1`;
if (engagedSetting.length > 0) {
setCleanupIncludeEngaged(engagedSetting[0].value === 'true');
}
console.log(`[BOOT] Cleanup Include Engaged: ${getCleanupIncludeEngaged()}`);
} catch (e) {
console.warn(`[BOOT] Cleanup settings fetch failed:`, e.message);
setEnableCleanup(!!cfg.websrv.enable_cleanup);
@@ -1392,6 +1398,7 @@ process.on('uncaughtException', err => {
get enable_cleanup() { return getEnableCleanup(); },
get cleanup_start_date() { return getCleanupStartDate(); },
get cleanup_end_date() { return getCleanupEndDate(); },
get cleanup_include_engaged() { return getCleanupIncludeEngaged(); },
matrix_enabled: cfg.clients.find(c => c.type === 'matrix')?.enabled || false,
telegram_enabled: cfg.clients.find(c => c.type === 'tg')?.enabled || false,
ts: Date.now(),

View File

@@ -26,6 +26,14 @@
</div>
</div>
<div class="settings-item" style="margin-bottom: 20px;">
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; color: #ff6b6b; font-weight: bold;">
<input type="checkbox" id="cleanup_include_engaged" name="cleanup_include_engaged" value="true" @if(cleanup_include_engaged) checked @endif style="width: 18px; height: 18px; cursor: pointer;">
<span>Include items with engagement</span>
</label>
<span style="color: #888; display: block; margin-top: 5px; margin-left: 28px;">If checked, cleanup will purge ALL posts in the selected date range, even if they have comments or favorites.</span>
</div>
<div style="display: flex; gap: 10px; align-items: center;">
<button type="submit" class="btn-primary" style="width: auto; padding: 12px 40px; font-weight: bold;">Save Configuration</button>
<span id="cleanup-status" style="margin-left: 15px; font-weight: bold; display: none;"></span>
@@ -101,8 +109,13 @@
async function runCleanup() {
const btn = document.getElementById('run-cleanup-btn');
const status = document.getElementById('run-status');
const includeEngaged = document.getElementById('cleanup_include_engaged')?.checked || false;
if (!confirm('Are you absolutely sure? This will PERMANENTLY delete files from disk. This action cannot be undone.')) return;
const warningMsg = includeEngaged
? 'SECURITY CLEANUP WARNING: You have selected to INCLUDE posts WITH engagement (comments/favorites). Are you ABSOLUTELY SURE you want to permanently delete ALL posts in this date range?'
: 'Are you absolutely sure? This will PERMANENTLY delete files from disk. This action cannot be undone.';
if (!confirm(warningMsg)) return;
btn.disabled = true;
btn.textContent = 'CLEANING UP...';
@@ -110,12 +123,17 @@
status.style.color = 'var(--accent)';
try {
const formData = new URLSearchParams();
formData.append('include_engaged', includeEngaged ? 'true' : 'false');
const res = await fetch('/admin/cleanup/run', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRF-Token': window.f0ckSession?.csrf_token || '{{ csrf_token }}'
}
},
body: formData
});
const cleanup_response = await res.json();