add nsfp tag manager
This commit is contained in:
@@ -6,7 +6,7 @@ import queue from "../queue.mjs";
|
||||
import fs from "fs";
|
||||
import url from "url";
|
||||
|
||||
const globalfilter = cfg.nsfp?.length ? cfg.nsfp.map(n => `tag_id = ${n}`).join(" or ") : null;
|
||||
const getGlobalfilter = () => cfg.nsfp?.length ? cfg.nsfp.map(n => `tag_id = ${n}`).join(" or ") : null;
|
||||
|
||||
// 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);
|
||||
@@ -251,7 +251,7 @@ export default {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -293,7 +293,7 @@ export default {
|
||||
${mimeSQL}
|
||||
${hallFilter}
|
||||
${userHallFilter}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
${newerThan ? db`and items.id > ${newerThan}` : db``}
|
||||
${xdFilter}
|
||||
@@ -515,7 +515,7 @@ export default {
|
||||
${fav ? db`and "user"."user" ilike ${user}` : db``}
|
||||
${!fav && user ? db`and items.username ilike ${user}` : db``}
|
||||
${mimeSQL}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
`;
|
||||
};
|
||||
@@ -548,7 +548,7 @@ export default {
|
||||
where
|
||||
items.id = ${itemid} and
|
||||
items.active = true
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
limit 1
|
||||
`;
|
||||
|
||||
@@ -566,7 +566,7 @@ export default {
|
||||
|
||||
if (!actitem) {
|
||||
// Item not found or filtered out - check if it exists but was filtered (for OG meta tags)
|
||||
if (!session && globalfilter) {
|
||||
if (!session && getGlobalfilter()) {
|
||||
const unfilteredItem = await db`
|
||||
select id from items where id = ${itemid} and active = true limit 1
|
||||
`;
|
||||
@@ -917,7 +917,7 @@ export default {
|
||||
AND items.title ILIKE ${'%' + titleQuery + '%'}
|
||||
AND items.title IS NOT NULL
|
||||
${mimeSQL}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
ORDER BY random()
|
||||
LIMIT 1
|
||||
@@ -937,7 +937,7 @@ export default {
|
||||
and "user".user ilike ${user}
|
||||
and items.active = true
|
||||
${mimeSQL}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : db``}
|
||||
group by items.id
|
||||
order by random()
|
||||
limit 1
|
||||
@@ -979,7 +979,7 @@ export default {
|
||||
${user ? db`and items.username ilike ${user}` : db``}
|
||||
${hall ? db`and items.id in (select item_id from halls_assign ha join halls h on h.id = ha.hall_id where h.slug = ${hall})` : db``}
|
||||
${mimeSQL}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
group by items.id, tags.tag
|
||||
order by random()
|
||||
@@ -998,7 +998,7 @@ export default {
|
||||
and h.slug = ${hall}
|
||||
and items.active = true
|
||||
${mimeSQL}
|
||||
${!session && globalfilter ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(globalfilter)}))` : db``}
|
||||
${!session && getGlobalfilter() ? db`and not exists (select 1 from tags_assign where item_id = items.id and (${db.unsafe(getGlobalfilter())}))` : 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``}
|
||||
order by random()
|
||||
limit 1
|
||||
|
||||
@@ -10,7 +10,7 @@ import audit from '../../audit.mjs';
|
||||
import { parseMultipart, collectBody } from '../../multipart.mjs';
|
||||
|
||||
const allowedMimes = ["audio", "image", "video", "%"];
|
||||
const globalfilter = cfg.nsfp?.length ? cfg.nsfp.map(n => `tag_id = ${n}`).join(' or ') : null;
|
||||
const getGlobalfilter = () => cfg.nsfp?.length ? cfg.nsfp.map(n => `tag_id = ${n}`).join(' or ') : null;
|
||||
const metaCache = new Map();
|
||||
const MAX_META_CACHE = 2000;
|
||||
|
||||
|
||||
126
src/inc/routes/nsfp.mjs
Normal file
126
src/inc/routes/nsfp.mjs
Normal file
@@ -0,0 +1,126 @@
|
||||
import db from "../sql.mjs";
|
||||
import lib from "../lib.mjs";
|
||||
import audit from "../audit.mjs";
|
||||
import { getNsfpIds, setNsfpIds } from "../settings.mjs";
|
||||
|
||||
export default (router, tpl) => {
|
||||
|
||||
// Admin page
|
||||
router.get(/^\/admin\/nsfp\/?$/, lib.auth, async (req, res) => {
|
||||
try {
|
||||
res.reply({
|
||||
body: tpl.render("admin/nsfp", {
|
||||
session: req.session,
|
||||
totals: await lib.countf0cks(),
|
||||
csrf_token: req.session?.csrf_token || ''
|
||||
}, req)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[NSFP] Page render failed:', err);
|
||||
res.reply({ code: 500, body: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// API: list current NSFP tag IDs with names
|
||||
router.get('/api/v2/admin/nsfp', lib.auth, async (req, res) => {
|
||||
try {
|
||||
const ids = getNsfpIds();
|
||||
const tagRows = ids.length > 0
|
||||
? await db`SELECT id, tag, normalized FROM tags WHERE id IN ${db(ids)} ORDER BY id`
|
||||
: [];
|
||||
const tagMap = Object.fromEntries(tagRows.map(r => [r.id, r]));
|
||||
const enriched = ids.map(id => tagMap[id] || { id, tag: '(unknown)', normalized: null });
|
||||
return res.json({ success: true, nsfp: enriched, raw_ids: ids });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// API: search tags for the add-tag autocomplete
|
||||
router.get('/api/v2/admin/nsfp/search', lib.auth, async (req, res) => {
|
||||
try {
|
||||
const q = (req.url.qs?.q || '').trim();
|
||||
if (!q) return res.json({ success: true, tags: [] });
|
||||
const pattern = '%' + q + '%';
|
||||
const tags = await db`
|
||||
SELECT id, tag, normalized
|
||||
FROM tags
|
||||
WHERE tag ILIKE ${pattern} OR normalized ILIKE ${pattern}
|
||||
ORDER BY tag
|
||||
LIMIT 20
|
||||
`;
|
||||
return res.json({ success: true, tags });
|
||||
} catch (err) {
|
||||
return res.json({ success: false, msg: err.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// API: add a tag ID to the NSFP list
|
||||
router.post('/api/v2/admin/nsfp/add', lib.auth, async (req, res) => {
|
||||
try {
|
||||
const tagId = parseInt(req.post?.tag_id);
|
||||
if (!tagId || isNaN(tagId) || tagId <= 0) {
|
||||
return res.json({ success: false, msg: 'A valid tag_id is required' }, 400);
|
||||
}
|
||||
|
||||
const tag = await db`SELECT id, tag FROM tags WHERE id = ${tagId} LIMIT 1`;
|
||||
if (tag.length === 0) {
|
||||
return res.json({ success: false, msg: 'Tag with id ' + tagId + ' does not exist' }, 404);
|
||||
}
|
||||
|
||||
const current = getNsfpIds();
|
||||
if (current.includes(tagId)) {
|
||||
return res.json({ success: false, msg: 'Tag #' + tagId + ' (' + tag[0].tag + ') is already in the NSFP list' }, 409);
|
||||
}
|
||||
|
||||
const updated = [...current, tagId];
|
||||
setNsfpIds(updated);
|
||||
|
||||
await db`
|
||||
INSERT INTO site_settings (key, value)
|
||||
VALUES ('nsfp', ${JSON.stringify(updated)})
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
||||
`;
|
||||
|
||||
await audit.log(req.session.id, 'nsfp_add', 'tag', tagId, { tag: tag[0].tag, nsfp: updated });
|
||||
|
||||
return res.json({ success: true, nsfp_ids: getNsfpIds(), added: tag[0] });
|
||||
} catch (err) {
|
||||
console.error('[NSFP] Add failed:', err);
|
||||
return res.json({ success: false, msg: err.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
// API: remove a tag ID from the NSFP list
|
||||
router.post('/api/v2/admin/nsfp/remove', lib.auth, async (req, res) => {
|
||||
try {
|
||||
const tagId = parseInt(req.post?.tag_id);
|
||||
if (!tagId || isNaN(tagId)) {
|
||||
return res.json({ success: false, msg: 'tag_id is required' }, 400);
|
||||
}
|
||||
|
||||
const current = getNsfpIds();
|
||||
if (!current.includes(tagId)) {
|
||||
return res.json({ success: false, msg: 'Tag #' + tagId + ' is not in the NSFP list' }, 404);
|
||||
}
|
||||
|
||||
const updated = current.filter(id => id !== tagId);
|
||||
setNsfpIds(updated);
|
||||
|
||||
await db`
|
||||
INSERT INTO site_settings (key, value)
|
||||
VALUES ('nsfp', ${JSON.stringify(updated)})
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
||||
`;
|
||||
|
||||
await audit.log(req.session.id, 'nsfp_remove', 'tag', tagId, { nsfp: updated });
|
||||
|
||||
return res.json({ success: true, nsfp_ids: getNsfpIds() });
|
||||
} catch (err) {
|
||||
console.error('[NSFP] Remove failed:', err);
|
||||
return res.json({ success: false, msg: err.message }, 500);
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
@@ -89,3 +89,13 @@ export const setHashUserIps = (val) => {}; // No-op, strictly config-based
|
||||
export const getAllowCommentDeletion = () => !!cfg.websrv.allow_comment_deletion;
|
||||
export const setAllowCommentDeletion = (val) => {}; // No-op, strictly config-based
|
||||
|
||||
// Live-editable NSFP tag ID list — seeded from config.json, can be overridden by DB setting
|
||||
let nsfp_ids = Array.isArray(cfg.nsfp) ? [...cfg.nsfp.map(Number).filter(n => !isNaN(n))] : [];
|
||||
|
||||
export const getNsfpIds = () => nsfp_ids;
|
||||
export const setNsfpIds = (ids) => {
|
||||
nsfp_ids = Array.isArray(ids) ? ids.map(Number).filter(n => !isNaN(n) && n > 0) : [];
|
||||
// Also sync to cfg.nsfp so all code reading cfg.nsfp directly still works
|
||||
cfg.nsfp = [...nsfp_ids];
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,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 } 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, 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";
|
||||
@@ -1087,6 +1087,22 @@ process.on('uncaughtException', err => {
|
||||
console.warn(`[BOOT] Terms text fetch failed:`, e.message);
|
||||
}
|
||||
|
||||
// Fetch nsfp (NSFP tag IDs) setting — overrides config.json if set in DB
|
||||
try {
|
||||
const nsfpSetting = await db`SELECT value FROM site_settings WHERE key = 'nsfp' LIMIT 1`;
|
||||
if (nsfpSetting.length > 0) {
|
||||
const parsed = JSON.parse(nsfpSetting[0].value);
|
||||
if (Array.isArray(parsed)) {
|
||||
setNsfpIds(parsed);
|
||||
console.log(`[BOOT] NSFP tag IDs loaded from DB: [${getNsfpIds().join(', ')}]`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[BOOT] No NSFP setting in DB, using config.json: [${getNsfpIds().join(', ')}]`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[BOOT] NSFP setting fetch failed:`, e.message);
|
||||
}
|
||||
|
||||
const globals = {
|
||||
lul: cfg.websrv.lul,
|
||||
themes: cfg.websrv.themes,
|
||||
|
||||
Reference in New Issue
Block a user