Testing: allow selecting multiple ratings for better filtering
This commit is contained in:
@@ -81,6 +81,37 @@ export default new class {
|
||||
}
|
||||
return tmp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a multi-rating SQL WHERE clause fragment from an array of rating strings.
|
||||
* Supported values: 'sfw', 'nsfw', 'nsfl', 'untagged'
|
||||
* Returns null if the ratings array is empty or contains all possible values (treat as ALL).
|
||||
*/
|
||||
getMultiRatingMode(ratings) {
|
||||
if (!Array.isArray(ratings) || ratings.length === 0) return null;
|
||||
const valid = ['sfw', 'nsfw', 'nsfl', 'untagged'];
|
||||
const filtered = ratings.filter(r => valid.includes(r));
|
||||
if (filtered.length === 0) return null;
|
||||
// If all 4 are selected, treat as ALL
|
||||
if (filtered.includes('sfw') && filtered.includes('nsfw') && filtered.includes('untagged') &&
|
||||
(!cfg.enable_nsfl || filtered.includes('nsfl'))) return '1 = 1';
|
||||
|
||||
const parts = [];
|
||||
if (filtered.includes('sfw')) {
|
||||
parts.push('items.id in (select item_id from tags_assign where tag_id = 1)');
|
||||
}
|
||||
if (filtered.includes('nsfw')) {
|
||||
parts.push('items.id in (select item_id from tags_assign where tag_id = 2)');
|
||||
}
|
||||
if (filtered.includes('nsfl') && cfg.enable_nsfl) {
|
||||
parts.push(`items.id in (select item_id from tags_assign where tag_id = ${parseInt(cfg.nsfl_tag_id, 10) || 3})`);
|
||||
}
|
||||
if (filtered.includes('untagged')) {
|
||||
parts.push('not exists (select 1 from tags_assign where item_id = items.id)');
|
||||
}
|
||||
if (parts.length === 0) return null;
|
||||
return '(' + parts.join(' OR ') + ')';
|
||||
};
|
||||
createID() {
|
||||
return crypto.randomBytes(16).toString("hex") + Date.now().toString(24);
|
||||
};
|
||||
|
||||
@@ -95,7 +95,7 @@ const xdScoreMeta = (score) => {
|
||||
};
|
||||
|
||||
export default {
|
||||
getf0cks: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, page, mode, fav, session, limit, strict, newer, exclude, user_id, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, minXdScore } = {}) => {
|
||||
getf0cks: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, page, mode, ratings, fav, session, limit, strict, newer, exclude, user_id, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, minXdScore } = {}) => {
|
||||
const user = rawUser ? lib.escapeLike(decodeURI(rawUser)) : null;
|
||||
|
||||
// --- title: prefix — search items.title instead of the tags table ---
|
||||
@@ -149,7 +149,9 @@ export default {
|
||||
const isStrict = strictParams.length > 0;
|
||||
|
||||
const tmp = { user, tag: isTitleSearch ? _decodedTag : tag, hall: hallObj || hall, mime, page: actPage, mode: mode, view_mode: fav ? 'favs' : 'uploads', strict: strict, userHall: userHallObj || userHallSlug, userHallOwner };
|
||||
const baseMode = lib.getMode(mode ?? 0);
|
||||
// Multi-rating support: if `ratings` array provided, build an OR-based SQL fragment
|
||||
const multiRatingSQL = (Array.isArray(ratings) && ratings.length > 0) ? lib.getMultiRatingMode(ratings) : null;
|
||||
const baseMode = multiRatingSQL ?? lib.getMode(mode ?? 0);
|
||||
const modequery = baseMode;
|
||||
|
||||
let tagFilter = db``;
|
||||
@@ -340,7 +342,7 @@ export default {
|
||||
view_mode: fav ? 'favs' : 'uploads'
|
||||
};
|
||||
},
|
||||
getf0ck: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, itemid: rawItemid, mode, session, strict, exclude, user_id, fav, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, lang } = {}) => {
|
||||
getf0ck: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, itemid: rawItemid, mode, ratings, session, strict, exclude, user_id, fav, random, userHall: rawUserHall, userHallOwner: rawUserHallOwner, lang } = {}) => {
|
||||
const user = rawUser ? lib.escapeLike(decodeURI(rawUser)) : null;
|
||||
|
||||
// --- title: prefix — search items.title instead of the tags table ---
|
||||
@@ -387,7 +389,8 @@ export default {
|
||||
const tmp = { user, tag: isTitleSearch ? _decodedTag : tag, hall, mime, itemid, strict: strict, userHall: userHallObj || userHallSlug, userHallOwner };
|
||||
|
||||
const effMode = Number(mode ?? 0);
|
||||
const modequery = lib.getMode(effMode);
|
||||
const multiRatingSQL = (Array.isArray(ratings) && ratings.length > 0) ? lib.getMultiRatingMode(ratings) : null;
|
||||
const modequery = multiRatingSQL ?? lib.getMode(effMode);
|
||||
|
||||
if (itemid === null) {
|
||||
return {
|
||||
@@ -739,7 +742,7 @@ export default {
|
||||
tmp
|
||||
};
|
||||
return data;
|
||||
}, getRandom: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, mode, fav, session, strict, exclude, userHall: rawUserHall, userHallOwner: rawUserHallOwner } = {}) => {
|
||||
}, getRandom: async ({ user: rawUser, tag: rawTag, hall: rawHall, mime: rawMime, mode, ratings, fav, session, strict, exclude, userHall: rawUserHall, userHallOwner: rawUserHallOwner } = {}) => {
|
||||
const user = rawUser ? lib.escapeLike(decodeURI(rawUser)) : null;
|
||||
const hall = rawHall || null;
|
||||
|
||||
@@ -779,7 +782,8 @@ export default {
|
||||
const strictParams = ((strict || (tag && tag.includes(','))) && tag) ? tag.split(',').map(t => lib.slugify(t)).filter(t => t) : [];
|
||||
const isStrict = strictParams.length > 0;
|
||||
|
||||
const baseMode = lib.getMode(mode ?? 0);
|
||||
const multiRatingSQL = (Array.isArray(ratings) && ratings.length > 0) ? lib.getMultiRatingMode(ratings) : null;
|
||||
const baseMode = multiRatingSQL ?? lib.getMode(mode ?? 0);
|
||||
const modequery = baseMode;
|
||||
|
||||
let item;
|
||||
@@ -897,10 +901,13 @@ export default {
|
||||
limit 1
|
||||
`;
|
||||
} else {
|
||||
// Uniform random logic for global requests (no user/tag)
|
||||
const baseMode = lib.getMode(mode ?? 0);
|
||||
const modequery = baseMode;
|
||||
const tagId = (mode === 0 || mode === 1 || mode === 4) ? (mode === 4 ? (cfg.nsfl_tag_id || 3) : (mode === 1 ? 2 : 1)) : null;
|
||||
// Uniform random logic for global requests (no user/tag/hall)
|
||||
// When multi-rating SQL is active, use it directly. Otherwise use the tag-join optimisation.
|
||||
const globalModeQuery = multiRatingSQL ?? lib.getMode(mode ?? 0);
|
||||
// tagId optimisation only applies for single native modes (not multi-rating)
|
||||
const tagId = !multiRatingSQL && (mode === 0 || mode === 1 || mode === 4)
|
||||
? (mode === 4 ? (cfg.nsfl_tag_id || 3) : (mode === 1 ? 2 : 1))
|
||||
: null;
|
||||
// If audio is included, we avoid the strict tagId optimization to ensure audio is visible
|
||||
const useTagIdOpt = tagId && !mimeParts.includes('audio');
|
||||
const nsfpIds = cfg.nsfp || [];
|
||||
@@ -917,7 +924,7 @@ export default {
|
||||
${mimeSQL}
|
||||
${checkFilter ? db`AND filter_ta.tag_id IS NULL` : 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``}
|
||||
${!useTagIdOpt ? db`AND ${db.unsafe(modequery)}` : db``}
|
||||
${!useTagIdOpt ? db`AND ${db.unsafe(globalModeQuery)}` : db``}
|
||||
ORDER BY random()
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
@@ -31,11 +31,14 @@ export default (router, tpl) => {
|
||||
if (cfg.main.development) console.log(`[${new Date().toISOString()}] [AJAX] Starting item load for ${req.params.itemid}`);
|
||||
|
||||
const isRandom = query.random === '1' || req.cookies.random_mode === '1';
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
|
||||
const itemid = req.params.itemid || req.url.pathname.match(/\/ajax\/item\/(\d+)/)?.[1];
|
||||
const data = await f0cklib.getf0ck({
|
||||
itemid: itemid,
|
||||
mode: query.mode !== undefined ? +query.mode : req.mode,
|
||||
ratings: ratingsArr,
|
||||
session: !!req.session,
|
||||
url: contextUrl,
|
||||
user: query.user,
|
||||
@@ -191,6 +194,8 @@ export default (router, tpl) => {
|
||||
|
||||
const page = parseInt(query.page) || 1;
|
||||
const isRandom = query.random === '1' || req.cookies.random_mode === '1';
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
|
||||
const data = await f0cklib.getf0cks({
|
||||
page: page,
|
||||
@@ -199,6 +204,7 @@ export default (router, tpl) => {
|
||||
user: query.user || null,
|
||||
mime: query.mime || (req.cookies.mime || null),
|
||||
mode: query.mode !== undefined ? +query.mode : req.mode,
|
||||
ratings: ratingsArr,
|
||||
session: !!req.session,
|
||||
exclude: req.session ? (req.session.excluded_tags || []) : [],
|
||||
user_id: req.session?.id,
|
||||
|
||||
@@ -497,6 +497,8 @@ export default router => {
|
||||
const isFav = req.url.qs.fav === 'true';
|
||||
const isStrict = req.url.qs.strict === '1';
|
||||
const mode = req.session?.mode ?? 0;
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
|
||||
const data = await f0cklib.getRandom({
|
||||
user,
|
||||
@@ -507,6 +509,7 @@ export default router => {
|
||||
mime,
|
||||
fav: isFav,
|
||||
mode,
|
||||
ratings: ratingsArr && ratingsArr.length > 0 ? ratingsArr : null,
|
||||
strict: isStrict,
|
||||
session: !!req.session,
|
||||
exclude: req.session?.excluded_tags || []
|
||||
|
||||
@@ -146,7 +146,14 @@ export default (router, tpl) => {
|
||||
mode = parseInt(req.url.qs.mode);
|
||||
}
|
||||
/* </mode-override> */
|
||||
const modequery = lib.getMode(mode).replace(/items\.id/g, 'i.id');
|
||||
|
||||
// Multi-rating cookie support (same logic as other routes)
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
const multiRatingSQL = (ratingsArr && ratingsArr.length > 0) ? lib.getMultiRatingMode(ratingsArr) : null;
|
||||
|
||||
// Build mode SQL — replace items.id alias with i.id used in the activity query
|
||||
const modequery = (multiRatingSQL ?? lib.getMode(mode)).replace(/items\.id/g, 'i.id');
|
||||
|
||||
const comments = await db`
|
||||
SELECT c.*, i.mime, i.id as item_id
|
||||
@@ -843,7 +850,14 @@ export default (router, tpl) => {
|
||||
mode = parseInt(req.url.qs.mode);
|
||||
}
|
||||
/* </mode-override> */
|
||||
const modequery = lib.getMode(mode).replace(/items\.id/g, 'i.id');
|
||||
|
||||
// Multi-rating cookie support (same logic as other routes)
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
const multiRatingSQL = (ratingsArr && ratingsArr.length > 0) ? lib.getMultiRatingMode(ratingsArr) : null;
|
||||
|
||||
// Build mode SQL — replace items.id alias with i.id used in the activity query
|
||||
const modequery = (multiRatingSQL ?? lib.getMode(mode)).replace(/items\.id/g, 'i.id');
|
||||
const globalfilter = cfg.nsfp.map(n => `tag_id = ${n}`).join(' or ');
|
||||
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
|
||||
|
||||
|
||||
@@ -62,9 +62,12 @@ export default (router, tpl) => {
|
||||
};
|
||||
try {
|
||||
const isRandom = req.cookies.random_mode === '1';
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
f0cks = await f0cklib.getf0cks({
|
||||
user: user,
|
||||
mode: req.mode,
|
||||
ratings: ratingsArr,
|
||||
mime: mime,
|
||||
fav: false,
|
||||
session: !!req.session,
|
||||
@@ -83,9 +86,12 @@ export default (router, tpl) => {
|
||||
if (!userData.is_ghost) {
|
||||
try {
|
||||
const isRandom = req.cookies.random_mode === '1';
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
favs = await f0cklib.getf0cks({
|
||||
user: user,
|
||||
mode: req.mode,
|
||||
ratings: ratingsArr,
|
||||
mime: mime,
|
||||
fav: true,
|
||||
session: !!req.session,
|
||||
@@ -224,6 +230,7 @@ export default (router, tpl) => {
|
||||
hall: req.params.hall,
|
||||
fav: req.params.mode == 'favs',
|
||||
mode: req.mode,
|
||||
ratings: (() => { const r = req.cookies.ratings; return r ? decodeURIComponent(r).split(/[|,]/).filter(x => ['sfw','nsfw','nsfl','untagged'].includes(x)) : null; })(),
|
||||
session: !!req.session,
|
||||
user_id: req.session?.id,
|
||||
exclude: req.session ? (req.session.excluded_tags || []) : [],
|
||||
|
||||
@@ -39,6 +39,10 @@ export default (router, tpl) => {
|
||||
}
|
||||
}
|
||||
|
||||
const ratingsRaw = req.cookies.ratings;
|
||||
const ratingsArr = ratingsRaw ? decodeURIComponent(ratingsRaw).split(/[|,]/).filter(r => ['sfw','nsfw','nsfl','untagged'].includes(r)) : null;
|
||||
console.log('[RANDOM] ratings cookie:', ratingsRaw, '→ parsed:', ratingsArr);
|
||||
|
||||
const data = await f0cklib.getRandom({
|
||||
user: opts.user,
|
||||
tag: opts.tag,
|
||||
@@ -47,6 +51,7 @@ export default (router, tpl) => {
|
||||
page: opts.page,
|
||||
fav: opts.mode === 'favs',
|
||||
mode: req.mode,
|
||||
ratings: ratingsArr,
|
||||
strict: opts.strict,
|
||||
session: !!req.session
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user