fix rating and fav

This commit is contained in:
2026-08-13 21:14:20 +02:00
parent 402a043b57
commit 9ec07e8e6c
9 changed files with 53 additions and 17 deletions

View File

@@ -186,6 +186,9 @@
postid: postid postid: postid
}); });
if (res.success) { if (res.success) {
if (window.invalidateItemCache) {
window.invalidateItemCache(postid);
}
// New state is the logical opposite of what it was before the API call // New state is the logical opposite of what it was before the API call
const isNowFav = !wasAlreadyFav; const isNowFav = !wasAlreadyFav;

View File

@@ -4145,6 +4145,7 @@ window.cancelAnimFrame = (function () {
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
if (window.invalidateItemCache) window.invalidateItemCache(id);
const newOc = data.is_oc; const newOc = data.is_oc;
ocBtn.dataset.isOc = newOc; ocBtn.dataset.isOc = newOc;
ocBtn.setAttribute('title', newOc ? 'Remove OC status' : 'Mark as OC'); ocBtn.setAttribute('title', newOc ? 'Remove OC status' : 'Mark as OC');
@@ -4487,6 +4488,12 @@ window.cancelAnimFrame = (function () {
.then(res => { .then(res => {
toggleBtn._activeRequestsCount = Math.max(0, (toggleBtn._activeRequestsCount || 1) - 1); toggleBtn._activeRequestsCount = Math.max(0, (toggleBtn._activeRequestsCount || 1) - 1);
// Evict cached item HTML immediately on success so navigating back fetches fresh content,
// even if the user already navigated to a different item while this request was in flight.
if (res && res.success && window.invalidateItemCache) {
window.invalidateItemCache(postid);
}
// Verify we are still on the same post (prevent dynamic/PJAX page leaks) // Verify we are still on the same post (prevent dynamic/PJAX page leaks)
const currentIdStr = window.getCurrentItemId(); const currentIdStr = window.getCurrentItemId();
const currentPostId = currentIdStr ? parseInt(currentIdStr, 10) : null; const currentPostId = currentIdStr ? parseInt(currentIdStr, 10) : null;
@@ -4502,10 +4509,6 @@ window.cancelAnimFrame = (function () {
if (window.renderTags) { if (window.renderTags) {
window.renderTags(res.tags); window.renderTags(res.tags);
} }
// Evict the cached item HTML so navigating back fetches fresh content
if (window.invalidateItemCache) {
window.invalidateItemCache(postid);
}
} else { } else {
revert(); revert();
window.flashMessage('Error: ' + (res.msg || 'Failed to update rating'), 3000, 'error'); window.flashMessage('Error: ' + (res.msg || 'Failed to update rating'), 3000, 'error');
@@ -7148,14 +7151,21 @@ window.cancelAnimFrame = (function () {
window.loadPageAjax = loadPageAjax; window.loadPageAjax = loadPageAjax;
window.loadItemAjax = loadItemAjax; window.loadItemAjax = loadItemAjax;
// Invalidate all itemCacheMap entries that contain a given item ID. // Invalidate all itemCacheMap entries that match a given item ID or slug.
// Call this after mutating an item (e.g. adding a tag) so the next // Call this after mutating an item (e.g. rating, favorite, tags, OC) so the next
// AJAX navigation fetches fresh HTML instead of serving stale cache. // AJAX navigation fetches fresh HTML instead of serving stale cache.
window.invalidateItemCache = (itemId) => { window.invalidateItemCache = (itemId) => {
if (!itemId) return; if (itemId === undefined || itemId === null || itemId === '') return;
const needle = `/${itemId}`; const strId = String(itemId).trim();
for (const key of itemCacheMap.keys()) { for (const [key, val] of itemCacheMap.entries()) {
if (key.includes(needle)) { const urlPath = key.split('?')[0];
const segments = urlPath.split('/').filter(Boolean);
const lastSegment = segments[segments.length - 1];
if (lastSegment === strId) {
itemCacheMap.delete(key);
continue;
}
if (val && typeof val.html === 'string' && val.html.includes(`data-item-id="${strId}"`)) {
itemCacheMap.delete(key); itemCacheMap.delete(key);
} }
} }
@@ -8697,6 +8707,10 @@ class NotificationSystem {
return; return;
} }
if (window.invalidateItemCache) {
window.invalidateItemCache(data.item_id);
}
// Check if we are currently viewing this item // Check if we are currently viewing this item
const currentIdStr = window.getCurrentItemId(); const currentIdStr = window.getCurrentItemId();
const currentId = currentIdStr ? parseInt(currentIdStr, 10) : null; const currentId = currentIdStr ? parseInt(currentIdStr, 10) : null;
@@ -8780,6 +8794,10 @@ class NotificationSystem {
handleFavoritesUpdate(data) { handleFavoritesUpdate(data) {
if (!data || !data.item_id) return; if (!data || !data.item_id) return;
if (window.invalidateItemCache) {
window.invalidateItemCache(data.item_id);
}
// Check if we are currently viewing this item // Check if we are currently viewing this item
const currentIdStr = window.getCurrentItemId(); const currentIdStr = window.getCurrentItemId();
if (!currentIdStr || parseInt(currentIdStr, 10) !== parseInt(data.item_id, 10)) return; if (!currentIdStr || parseInt(currentIdStr, 10) !== parseInt(data.item_id, 10)) return;

View File

@@ -175,6 +175,9 @@
postid: postid postid: postid
}); });
if (res.success) { if (res.success) {
if (window.invalidateItemCache) {
window.invalidateItemCache(postid);
}
// New state is the logical opposite of what it was before the API call // New state is the logical opposite of what it was before the API call
const isNowFav = !wasAlreadyFav; const isNowFav = !wasAlreadyFav;

View File

@@ -200,27 +200,27 @@ export default new class {
const tagged = +(await db` const tagged = +(await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign group by item_id) and active = true where id in (select item_id from tags_assign group by item_id) and active = true and is_deleted = false
`)[0].total; `)[0].total;
const untagged = +(await db` const untagged = +(await db`
select count(*) as total select count(*) as total
from "items" from "items"
where not exists (select 1 from tags_assign where item_id = items.id) and active = true where not exists (select 1 from tags_assign where item_id = items.id) and active = true and is_deleted = false
`)[0].total; `)[0].total;
const sfw = +(await db` const sfw = +(await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign where tag_id = 1 group by item_id) and active = true where id in (select item_id from tags_assign where tag_id = 1 group by item_id) and active = true and is_deleted = false
`)[0].total; `)[0].total;
const nsfw = +(await db` const nsfw = +(await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign where tag_id = 2 group by item_id) and active = true where id in (select item_id from tags_assign where tag_id = 2 group by item_id) and active = true and is_deleted = false
`)[0].total; `)[0].total;
const nsfl = cfg.enable_nsfl ? +(await db` const nsfl = cfg.enable_nsfl ? +(await db`
select count(*) as total select count(*) as total
from "items" from "items"
where id in (select item_id from tags_assign where tag_id = ${cfg.nsfl_tag_id || 3} group by item_id) and active = true where id in (select item_id from tags_assign where tag_id = ${cfg.nsfl_tag_id || 3} group by item_id) and active = true and is_deleted = false
`)[0].total : 0; `)[0].total : 0;
const deleted = +(await db` const deleted = +(await db`
select count(*) as total select count(*) as total

View File

@@ -845,12 +845,16 @@ export default (router, tpl) => {
await fs.unlink(path.join(cfg.paths.ca, `${item.id}.webp`)).catch(() => { }); await fs.unlink(path.join(cfg.paths.ca, `${item.id}.webp`)).catch(() => { });
} }
await db`DELETE FROM tags_assign WHERE item_id = ${item.id}`;
await db`UPDATE items SET is_deleted = true, is_purged = true, active = false WHERE id = ${item.id}`; await db`UPDATE items SET is_deleted = true, is_purged = true, active = false WHERE id = ${item.id}`;
count++; count++;
} catch (e) { } catch (e) {
console.error(`[CLEANUP] Failed to delete item ${item.id}:`, e.message); console.error(`[CLEANUP] Failed to delete item ${item.id}:`, e.message);
} }
} }
// Clean up any remaining orphan tag assignments for deleted or inactive items
await db`DELETE FROM tags_assign WHERE item_id IN (SELECT id FROM items WHERE is_deleted = true OR active = false)`;
} }
// Log it in audit // Log it in audit

View File

@@ -115,10 +115,13 @@ export default (router, tpl) => {
try { try {
const slug = '%' + lib.slugify(q) + '%'; const slug = '%' + lib.slugify(q) + '%';
const rows = await db` const rows = await db`
SELECT t.tag, t.normalized, COUNT(ta.item_id) as uses SELECT t.tag, t.normalized, COUNT(DISTINCT items.id) as uses
FROM tags t FROM tags t
JOIN tags_assign ta ON ta.tag_id = t.id JOIN tags_assign ta ON ta.tag_id = t.id
JOIN items ON items.id = ta.item_id
WHERE t.id > 2 WHERE t.id > 2
AND items.active = true
AND items.is_deleted = false
AND lower(t.normalized) ILIKE ${slug} AND lower(t.normalized) ILIKE ${slug}
GROUP BY t.tag, t.normalized GROUP BY t.tag, t.normalized
ORDER BY uses DESC ORDER BY uses DESC

View File

@@ -33,6 +33,7 @@ export async function regenerateTagImage(tag, mode) {
${modeFilter} ${modeFilter}
WHERE (t.tag = ${tag} OR t.normalized = ${tag}) WHERE (t.tag = ${tag} OR t.normalized = ${tag})
AND i.active = true AND i.active = true
AND i.is_deleted = false
AND COALESCE(i.visibility, 0) = 0 AND COALESCE(i.visibility, 0) = 0
ORDER BY RANDOM() ORDER BY RANDOM()
LIMIT 3 LIMIT 3

View File

@@ -51,6 +51,7 @@ export default (router, tpl) => {
JOIN tags_assign ta ON t.id = ta.tag_id JOIN tags_assign ta ON t.id = ta.tag_id
JOIN items ON items.id = ta.item_id JOIN items ON items.id = ta.item_id
WHERE items.active = true WHERE items.active = true
AND items.is_deleted = false
AND COALESCE(items.visibility, 0) = 0 AND COALESCE(items.visibility, 0) = 0
AND t.id NOT IN (1, 2) AND t.id NOT IN (1, 2)
AND ${db.unsafe(modequery)} AND ${db.unsafe(modequery)}
@@ -75,6 +76,7 @@ export default (router, tpl) => {
JOIN items ON items.id = ta.item_id JOIN items ON items.id = ta.item_id
WHERE t.normalized LIKE '%' || ${tag.normalized} || '%' WHERE t.normalized LIKE '%' || ${tag.normalized} || '%'
AND items.active = true AND items.active = true
AND items.is_deleted = false
AND COALESCE(items.visibility, 0) = 0 AND COALESCE(items.visibility, 0) = 0
AND ${db.unsafe(modequery)} AND ${db.unsafe(modequery)}
${restrictedFilter} ${restrictedFilter}

View File

@@ -51,6 +51,7 @@ export default (router, tpl) => {
JOIN tags_assign ta ON t.id = ta.tag_id JOIN tags_assign ta ON t.id = ta.tag_id
JOIN items ON items.id = ta.item_id JOIN items ON items.id = ta.item_id
WHERE items.active = true WHERE items.active = true
AND items.is_deleted = false
AND COALESCE(items.visibility, 0) = 0 AND COALESCE(items.visibility, 0) = 0
AND t.id NOT IN (1, 2) AND t.id NOT IN (1, 2)
AND ta.user_id = ${userId} AND ta.user_id = ${userId}
@@ -76,6 +77,7 @@ export default (router, tpl) => {
JOIN items ON items.id = ta.item_id JOIN items ON items.id = ta.item_id
WHERE t.normalized LIKE '%' || ${tag.normalized} || '%' WHERE t.normalized LIKE '%' || ${tag.normalized} || '%'
AND items.active = true AND items.active = true
AND items.is_deleted = false
AND COALESCE(items.visibility, 0) = 0 AND COALESCE(items.visibility, 0) = 0
AND ta.user_id = ${userId} AND ta.user_id = ${userId}
AND ${db.unsafe(modequery)} AND ${db.unsafe(modequery)}