gsdfgsdf
This commit is contained in:
+10
-2
@@ -290,16 +290,24 @@ export default new class {
|
||||
left join "tags" on "tags".id = "tags_assign".tag_id
|
||||
${hasSession ? db`left join "user" on "user".id = "tags_assign".user_id left join user_options uo on uo.user_id = "user".id` : db``}
|
||||
where "tags_assign".item_id = ${+itemid}
|
||||
order by (case when "tags".id = 1 then 0 when "tags".id = 2 then 1 when "tags".id = ${cfg.nsfl_tag_id || 3} then 2 else 3 end) asc, "tags".id asc
|
||||
order by (case when "tags".id = 1 then 0 when "tags".id = 2 then 1 when "tags".normalized = 'nsfl' then 2 else 3 end) asc, "tags".id asc
|
||||
`;
|
||||
let hasRating = false;
|
||||
const cleanTags = [];
|
||||
for (let t = 0; t < tags.length; t++) {
|
||||
const isRating = ['sfw', 'nsfw', 'nsfl'].includes(tags[t].normalized);
|
||||
if (isRating) {
|
||||
if (hasRating) continue;
|
||||
hasRating = true;
|
||||
}
|
||||
tags[t].badge = this.getBadge(tags[t]);
|
||||
if (!hasSession) {
|
||||
delete tags[t].user;
|
||||
delete tags[t].display_name;
|
||||
}
|
||||
cleanTags.push(tags[t]);
|
||||
}
|
||||
return tags;
|
||||
return cleanTags;
|
||||
};
|
||||
getBadge(tagObj) {
|
||||
if (tagObj.tag.startsWith(">"))
|
||||
|
||||
@@ -138,7 +138,7 @@ export default (router, tpl) => {
|
||||
const session = data.session;
|
||||
const item = data.item;
|
||||
data.is_mod_or_admin = !!(session && (session.admin || session.is_moderator));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || session.user === item.username));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || (session.user && item.username && session.user.toLowerCase() === item.username.toLowerCase())));
|
||||
data.can_extract_meta = !!(item.mime && item.mime.indexOf('flash') === -1 && !(item.mime.startsWith('application/') && cfg.mimes[item.mime] && !['swf', 'pdf'].includes(cfg.mimes[item.mime])));
|
||||
data.user_has_favorited = !!(session && Array.isArray(item.favorites) && item.favorites.some(f => f.user === session.user));
|
||||
data.halls_slugs = Array.isArray(item.halls) ? item.halls.map(h => h.slug).join(',') : '';
|
||||
|
||||
@@ -650,12 +650,28 @@ export default router => {
|
||||
});
|
||||
}
|
||||
|
||||
const rows = await db`
|
||||
SELECT *
|
||||
FROM "items"
|
||||
WHERE id = ${data.itemid} AND active = true
|
||||
LIMIT 1
|
||||
`;
|
||||
// Run item fetch + page lookup in parallel — saves one sequential DB round-trip
|
||||
const [rows, itemPage] = await Promise.all([
|
||||
db`
|
||||
SELECT *
|
||||
FROM "items"
|
||||
WHERE id = ${data.itemid} AND active = true
|
||||
LIMIT 1
|
||||
`,
|
||||
f0cklib.getItemPage({
|
||||
targetItemId: data.itemid,
|
||||
user, tag, hall, userHall, userHallOwner, mime,
|
||||
fav: isFav,
|
||||
mode,
|
||||
ratings: ratingsArr && ratingsArr.length > 0 ? ratingsArr : null,
|
||||
strict: isStrict,
|
||||
session: !!req.session,
|
||||
exclude: req.session?.excluded_tags || [],
|
||||
user_id: req.session?.id,
|
||||
is_admin: req.session?.admin
|
||||
}).catch(() => 1)
|
||||
]);
|
||||
|
||||
const item = rows[0];
|
||||
|
||||
if (!item) {
|
||||
@@ -685,7 +701,8 @@ export default router => {
|
||||
slug: (getEnableItemSlugs() && item.slug) ? item.slug : null,
|
||||
dest: relativeDest,
|
||||
url: directUrl,
|
||||
direct_url: directUrl
|
||||
direct_url: directUrl,
|
||||
page: itemPage
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1436,49 +1453,63 @@ export default router => {
|
||||
return res.json({ success: false, msg: 'Item not found' }, 404);
|
||||
}
|
||||
|
||||
const isOwner = item[0].username === req.session.user;
|
||||
const isAdmin = req.session.admin || req.session.is_moderator;
|
||||
const isOwner = !!(item[0].username && req.session.user && item[0].username.toLowerCase() === req.session.user.toLowerCase());
|
||||
const isAdmin = !!(req.session.admin || req.session.is_moderator);
|
||||
|
||||
if (!isOwner && !isAdmin) {
|
||||
return res.json({ success: false, msg: 'Unauthorized' }, 403);
|
||||
}
|
||||
|
||||
const nsfl_id = cfg.nsfl_tag_id || 3;
|
||||
const existingRating = await db`
|
||||
SELECT tag_id FROM tags_assign
|
||||
WHERE item_id = ${itemid} AND tag_id IN (1, 2, ${nsfl_id})
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const currentRatingId = existingRating.length > 0 ? existingRating[0].tag_id : null;
|
||||
const nsflTagRow = await db`SELECT id FROM tags WHERE normalized = 'nsfl' LIMIT 1`;
|
||||
const nsfl_id = nsflTagRow.length > 0 ? nsflTagRow[0].id : (cfg.nsfl_tag_id || 11517);
|
||||
let newRatingId;
|
||||
const reqRating = req.body?.rating || req.post?.rating || req.url?.qs?.rating;
|
||||
if (reqRating === 'sfw') {
|
||||
newRatingId = 1;
|
||||
} else if (reqRating === 'nsfw') {
|
||||
newRatingId = 2;
|
||||
} else if (reqRating === 'nsfl') {
|
||||
newRatingId = nsfl_id;
|
||||
} else {
|
||||
// fallback to cycling
|
||||
if (currentRatingId === 1) {
|
||||
newRatingId = 2; // SFW -> NSFW
|
||||
} else if (currentRatingId === 2) {
|
||||
newRatingId = cfg.enable_nsfl ? nsfl_id : 1; // NSFW -> NSFL (if enabled) or SFW
|
||||
} else {
|
||||
newRatingId = 1; // NSFL or none -> SFW
|
||||
}
|
||||
}
|
||||
let currentRatingId = null;
|
||||
|
||||
await db.begin(async sql => {
|
||||
// Remove old rating tags
|
||||
await sql`DELETE FROM tags_assign WHERE item_id = ${itemid} AND tag_id IN (1, 2, ${nsfl_id})`;
|
||||
// Lock the item row exclusively so any concurrent rating update for this post MUST wait
|
||||
await sql`SELECT id FROM items WHERE id = ${itemid} FOR UPDATE`;
|
||||
|
||||
const existingRating = await sql`
|
||||
SELECT tag_id FROM tags_assign
|
||||
WHERE item_id = ${itemid}
|
||||
AND (tag_id IN (1, 2, ${nsfl_id}) OR tag_id IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl')))
|
||||
ORDER BY tag_id DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
currentRatingId = existingRating.length > 0 ? existingRating[0].tag_id : null;
|
||||
const reqRating = req.body?.rating || req.post?.rating || req.url?.qs?.rating;
|
||||
if (reqRating === 'sfw') {
|
||||
newRatingId = 1;
|
||||
} else if (reqRating === 'nsfw') {
|
||||
newRatingId = 2;
|
||||
} else if (reqRating === 'nsfl') {
|
||||
newRatingId = nsfl_id;
|
||||
} else {
|
||||
// fallback to cycling
|
||||
if (currentRatingId === 1) {
|
||||
newRatingId = 2; // SFW -> NSFW
|
||||
} else if (currentRatingId === 2) {
|
||||
newRatingId = cfg.enable_nsfl ? nsfl_id : 1; // NSFW -> NSFL (if enabled) or SFW
|
||||
} else {
|
||||
newRatingId = 1; // NSFL or none -> SFW
|
||||
}
|
||||
}
|
||||
|
||||
// Remove ALL existing rating tags for this item atomically
|
||||
await sql`
|
||||
DELETE FROM tags_assign
|
||||
WHERE item_id = ${itemid}
|
||||
AND (tag_id IN (1, 2, ${nsfl_id}) OR tag_id IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl')))
|
||||
`;
|
||||
|
||||
// Insert new rating tag
|
||||
await sql`
|
||||
INSERT INTO tags_assign (item_id, tag_id, user_id)
|
||||
VALUES (${itemid}, ${newRatingId}, ${req.session.id})
|
||||
`;
|
||||
if (newRatingId > 0) {
|
||||
await sql`
|
||||
INSERT INTO tags_assign (item_id, tag_id, user_id)
|
||||
VALUES (${itemid}, ${newRatingId}, ${req.session.id})
|
||||
`;
|
||||
}
|
||||
|
||||
// Ensure blurred thumbnail exists
|
||||
await queue.genBlurredThumbnail(itemid).catch(err => {
|
||||
|
||||
@@ -97,53 +97,91 @@ export default router => {
|
||||
});
|
||||
});
|
||||
|
||||
group.put(/\/cycle-rating$/, lib.modAuth, async (req, res) => {
|
||||
group.put(/\/cycle-rating$/, lib.loggedin, async (req, res) => {
|
||||
if (!req.params.postid) return res.json({ success: false, msg: 'missing postid' });
|
||||
|
||||
const postid = +req.params.postid;
|
||||
const nsflId = cfg.nsfl_tag_id || 3;
|
||||
// Cycle: SFW(1) → NSFW(2) → NSFL(nsflId) → SFW(1); untagged items jump straight to SFW
|
||||
const cycle = [1, 2, nsflId];
|
||||
const currentTags = await lib.getTags(postid);
|
||||
const ratingTagId = currentTags.find(t => [1, 2, nsflId].includes(t.id))?.id ?? 0;
|
||||
|
||||
let nextTagId;
|
||||
const reqRating = req.body?.rating || req.post?.rating || req.url?.qs?.rating;
|
||||
if (reqRating === 'sfw') {
|
||||
nextTagId = 1;
|
||||
} else if (reqRating === 'nsfw') {
|
||||
nextTagId = 2;
|
||||
} else if (reqRating === 'nsfl') {
|
||||
nextTagId = nsflId;
|
||||
} else {
|
||||
const cycleIdx = cycle.indexOf(ratingTagId); // -1 if untagged → (−1+1)%3 = 0 → SFW
|
||||
nextTagId = cycle[(cycleIdx + 1) % cycle.length];
|
||||
const item = await db`
|
||||
SELECT id, username, active, is_deleted
|
||||
FROM items
|
||||
WHERE id = ${postid} AND active = true AND is_deleted = false
|
||||
LIMIT 1
|
||||
`;
|
||||
if (item.length === 0) {
|
||||
return res.json({ success: false, msg: 'Item not found' }, 404);
|
||||
}
|
||||
|
||||
try {
|
||||
// Remove any existing rating tag
|
||||
await db`DELETE FROM tags_assign WHERE item_id = ${postid} AND tag_id = ANY(ARRAY[1, 2, ${nsflId}]::int[])`;
|
||||
if (nextTagId > 0) {
|
||||
await db`INSERT INTO tags_assign ${db({ tag_id: nextTagId, item_id: postid, user_id: +req.session.id })}`;
|
||||
}
|
||||
const isOwner = !!(item[0].username && req.session.user && item[0].username.toLowerCase() === req.session.user.toLowerCase());
|
||||
const isAdmin = !!(req.session.admin || req.session.is_moderator);
|
||||
if (!isOwner && !isAdmin) {
|
||||
return res.json({ success: false, msg: 'Unauthorized' }, 403);
|
||||
}
|
||||
|
||||
// Automatically generate/verify blurred thumbnail on cycle
|
||||
const blurPath = path.join(cfg.paths.t, `${postid}_blur.webp`);
|
||||
try {
|
||||
await fs.promises.access(blurPath);
|
||||
} catch {
|
||||
await queue.genBlurredThumbnail(postid, false);
|
||||
}
|
||||
const nsflTagRow = await db`SELECT id FROM tags WHERE normalized = 'nsfl' LIMIT 1`;
|
||||
const nsflId = nsflTagRow.length > 0 ? nsflTagRow[0].id : (cfg.nsfl_tag_id || 11517);
|
||||
const cycle = [1, 2, nsflId];
|
||||
let nextTagId;
|
||||
let ratingTagId = 0;
|
||||
|
||||
try {
|
||||
await db.begin(async sql => {
|
||||
// Lock the item row exclusively
|
||||
await sql`SELECT id FROM items WHERE id = ${postid} FOR UPDATE`;
|
||||
|
||||
const existingRating = await sql`
|
||||
SELECT tag_id FROM tags_assign
|
||||
WHERE item_id = ${postid}
|
||||
AND (tag_id IN (1, 2, ${nsflId}) OR tag_id IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl')))
|
||||
ORDER BY tag_id DESC
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
ratingTagId = existingRating.length > 0 ? existingRating[0].tag_id : 0;
|
||||
const reqRating = req.body?.rating || req.post?.rating || req.url?.qs?.rating;
|
||||
if (reqRating === 'sfw') {
|
||||
nextTagId = 1;
|
||||
} else if (reqRating === 'nsfw') {
|
||||
nextTagId = 2;
|
||||
} else if (reqRating === 'nsfl') {
|
||||
nextTagId = nsflId;
|
||||
} else {
|
||||
const cycleIdx = cycle.indexOf(ratingTagId); // -1 if untagged → (−1+1)%3 = 0 → SFW
|
||||
nextTagId = cycle[(cycleIdx + 1) % cycle.length];
|
||||
}
|
||||
|
||||
// Remove ALL existing rating tags for this item atomically
|
||||
await sql`
|
||||
DELETE FROM tags_assign
|
||||
WHERE item_id = ${postid}
|
||||
AND (tag_id IN (1, 2, ${nsflId}) OR tag_id IN (SELECT id FROM tags WHERE normalized IN ('sfw', 'nsfw', 'nsfl')))
|
||||
`;
|
||||
|
||||
if (nextTagId > 0) {
|
||||
await sql`
|
||||
INSERT INTO tags_assign (item_id, tag_id, user_id)
|
||||
VALUES (${postid}, ${nextTagId}, ${+req.session.id})
|
||||
`;
|
||||
}
|
||||
|
||||
// Automatically generate/verify blurred thumbnail on cycle
|
||||
const blurPath = path.join(cfg.paths.t, `${postid}_blur.webp`);
|
||||
try {
|
||||
await fs.promises.access(blurPath);
|
||||
} catch {
|
||||
await queue.genBlurredThumbnail(postid, false);
|
||||
}
|
||||
});
|
||||
|
||||
const labels = { 1: { label: 'SFW', cls: 'sfw' }, 2: { label: 'NSFW', cls: 'nsfw' }, [nsflId]: { label: 'NSFL', cls: 'nsfl' } };
|
||||
const { label, cls } = labels[nextTagId];
|
||||
const { label, cls } = labels[nextTagId] || { label: 'SFW', cls: 'sfw' };
|
||||
|
||||
await audit.log(req.session.id, 'cycle_rating', 'item', postid, { from: ratingTagId, to: nextTagId });
|
||||
await audit.log(req.session.id, 'cycle_rating', 'item', postid, { from: ratingTagId, to: nextTagId }).catch(() => {});
|
||||
const freshTags = await lib.getTags(postid);
|
||||
await db.notify('tags', JSON.stringify({ item_id: postid, fresh: true, tags: freshTags }));
|
||||
await db.notify('tags', JSON.stringify({ item_id: postid, fresh: true, tags: freshTags })).catch(() => {});
|
||||
|
||||
return res.json({ success: true, rating_tag_id: nextTagId, rating_label: label, rating_class: cls });
|
||||
} catch (err) {
|
||||
console.error('[CYCLE_RATING_ERROR]', err);
|
||||
return res.json({ success: false, msg: 'Failed to update rating' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -371,7 +371,7 @@ export default (router, tpl) => {
|
||||
// Is the current user a moderator/admin?
|
||||
data.is_mod_or_admin = !!(session && (session.admin || session.is_moderator));
|
||||
// Can the current user manage this item (owner, admin, or mod)?
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || session.user === item.username));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || (session.user && item.username && session.user.toLowerCase() === item.username.toLowerCase())));
|
||||
// Is the item's MIME type suitable for metadata extraction?
|
||||
// YouTube items use oEmbed via /meta/fetch; all non-flash MIME types are eligible.
|
||||
data.can_extract_meta = !!(item.mime && item.mime.indexOf('flash') === -1 && !(item.mime.startsWith('application/') && cfg.mimes[item.mime] && !['swf', 'pdf'].includes(cfg.mimes[item.mime])));
|
||||
|
||||
@@ -162,7 +162,7 @@ export default (router, tpl) => {
|
||||
const session = data.session;
|
||||
const item = data.item;
|
||||
data.is_mod_or_admin = !!(session && (session.admin || session.is_moderator));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || session.user === item.username));
|
||||
data.can_manage_item = !!(session && (session.admin || session.is_moderator || (session.user && item.username && session.user.toLowerCase() === item.username.toLowerCase())));
|
||||
data.can_extract_meta = !!(item.mime && item.mime.indexOf('flash') === -1 && !(item.mime.startsWith('application/') && cfg.mimes[item.mime] && !['swf', 'pdf'].includes(cfg.mimes[item.mime])));
|
||||
data.user_has_favorited = !!(session && Array.isArray(item.favorites) && item.favorites.some(f => f.user === session.user));
|
||||
data.halls_slugs = Array.isArray(item.halls) ? item.halls.map(h => h.slug).join(',') : '';
|
||||
|
||||
Reference in New Issue
Block a user