This commit is contained in:
2026-05-31 22:02:38 +02:00
parent 09fcf8d8ec
commit d594ac2edd
2 changed files with 72 additions and 21 deletions

View File

@@ -10,6 +10,46 @@ const globalfilter = cfg.nsfp?.length ? cfg.nsfp.map(n => `tag_id = ${n}`).join(
// All MIME types that map to the 'swf' extension in config (e.g. application/x-shockwave-flash, application/vnd.adobe.flash.movie) // All MIME types that map to the 'swf' extension in config (e.g. application/x-shockwave-flash, application/vnd.adobe.flash.movie)
const flashMimes = Object.entries(cfg.mimes || {}).filter(([, ext]) => ext === 'swf').map(([mime]) => mime); const flashMimes = Object.entries(cfg.mimes || {}).filter(([, ext]) => ext === 'swf').map(([mime]) => mime);
// ── Count cache ─────────────────────────────────────────────────────────────
// The COUNT(DISTINCT items.id) in getf0cks is expensive (full filtered scan).
// Cache it per unique filter combination for 90 seconds so that navigating
// between pages 1→192 with the same filters skips the COUNT entirely.
const COUNT_CACHE_TTL_MS = 90_000;
const countCache = new Map(); // key → { total, expiresAt }
function buildCountCacheKey({ modequery, tag, user, hall, mime, fav, session, excludedTags, newerThan, minXd, userHallObj }) {
return JSON.stringify([
modequery,
tag ?? '',
user ?? '',
hall ?? '',
mime ?? '',
fav ? 1 : 0,
session ? 1 : 0, // guests get globalfilter applied; members don't
excludedTags.slice().sort().join(','),
newerThan ?? '',
minXd,
userHallObj?.id ?? ''
]);
}
function getCachedCount(key) {
const entry = countCache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) { countCache.delete(key); return null; }
return entry.total;
}
function setCachedCount(key, total) {
countCache.set(key, { total, expiresAt: Date.now() + COUNT_CACHE_TTL_MS });
// Prevent unbounded growth — evict all expired entries when cache grows large
if (countCache.size > 500) {
const now = Date.now();
for (const [k, v] of countCache) { if (now > v.expiresAt) countCache.delete(k); }
}
}
// ────────────────────────────────────────────────────────────────────────────
const processMentions = async (comments) => { const processMentions = async (comments) => {
if (!comments || comments.length === 0) return comments; if (!comments || comments.length === 0) return comments;
@@ -192,26 +232,32 @@ export default {
userHallFilter = db`and items.id in (select uha.item_id from user_halls_assign uha where uha.hall_id = ${userHallObj.id})`; userHallFilter = db`and items.id in (select uha.item_id from user_halls_assign uha where uha.hall_id = ${userHallObj.id})`;
} }
const totalRows = await db` const cacheKey = buildCountCacheKey({ modequery, tag, user, hall, mime, fav, session, excludedTags, newerThan, minXd, userHallObj });
select count(distinct items.id) as total let total = getCachedCount(cacheKey);
from items
${fav ? db`inner join favorites on favorites.item_id = items.id inner join "user" fav_u on fav_u.id = favorites.user_id` : db``} if (total === null) {
where const totalRows = await db`
${db.unsafe(modequery)} select count(distinct items.id) as total
and items.active = true from items
${tagFilter} ${fav ? db`inner join favorites on favorites.item_id = items.id inner join "user" fav_u on fav_u.id = favorites.user_id` : db``}
${titleFilter} where
${fav ? db`and fav_u.user ilike ${user}` : db``} ${db.unsafe(modequery)}
${!fav && user ? db`and items.username ilike ${user}` : db``} and items.active = true
${mimeSQL} ${tagFilter}
${hallFilter} ${titleFilter}
${userHallFilter} ${fav ? db`and fav_u.user ilike ${user}` : db``}
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``} ${!fav && user ? db`and items.username ilike ${user}` : db``}
${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``} ${mimeSQL}
${newerThan ? db`and items.id > ${newerThan}` : db``} ${hallFilter}
${xdFilter} ${userHallFilter}
`; ${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
const total = Number(totalRows[0].total); ${excludedTags.length > 0 ? db`and not exists (select 1 from tags_assign where item_id = items.id and tag_id = any(${excludedTags}::int[]))` : db``}
${newerThan ? db`and items.id > ${newerThan}` : db``}
${xdFilter}
`;
total = Number(totalRows[0].total);
if (total > 0) setCachedCount(cacheKey, total);
}
if (!total || total === 0) { if (!total || total === 0) {
return { return {
@@ -1458,5 +1504,7 @@ export default {
}, },
computeXdScore, computeXdScore,
xdScoreMeta xdScoreMeta,
// Bust the count cache (call after a new upload is accepted so page totals stay accurate)
clearCountCache: () => countCache.clear()
}; };

View File

@@ -8,6 +8,7 @@ import path from "path";
import https from "https"; import https from "https";
import { getManualApproval, getMinTags, getTrustedUploads, getBypassDuplicateCheck, getEnablePdf } from "./inc/settings.mjs"; import { getManualApproval, getMinTags, getTrustedUploads, getBypassDuplicateCheck, getEnablePdf } from "./inc/settings.mjs";
import { parseMultipart, collectBody } from "./inc/multipart.mjs"; import { parseMultipart, collectBody } from "./inc/multipart.mjs";
import f0cklib from "./inc/routeinc/f0cklib.mjs";
// Helper for JSON response // Helper for JSON response
const sendJson = (res, data, code = 200) => { const sendJson = (res, data, code = 200) => {
@@ -560,6 +561,8 @@ export const handleUpload = async (req, res, self) => {
// Action if auto-approved // Action if auto-approved
if (!manualApproval) { if (!manualApproval) {
// Bust the count cache so page totals update immediately
f0cklib.clearCountCache();
if (!linkedToExisting) { if (!linkedToExisting) {
// Move logic: Handles both real files and symlinks (reposts) correctly // Move logic: Handles both real files and symlinks (reposts) correctly
const moveSafe = async (src, dst) => { const moveSafe = async (src, dst) => {