feat(tags): add blocked tags with purge integration #3
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/blocked-tags"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds the ability to mark specific tags as blocked, working like the existing blocked subreddits/users system. Content tagged with blocked tags is only deleted when the user runs the "Delete Blocked Content" purge — no immediate deletions during tagging.
Changes
Database (
db.js)blocked_tagstable (id,tagUNIQUE,created_at)Backend API
GET /api/tags/blocked— list blocked tagsPOST /api/tags/blocked— add{tag}to blocked listDELETE /api/tags/blocked— remove{tag}from blocked listGET /api/blocked/purge— preview now includesblockedTagscount and counts files with blocked auto-tags intotalFilesPOST /api/blocked/purge— appends blocked tags as{type: 'tag'}items to the purge job; newpurgeBlockedTag()async function deletes matching files (same pattern aspurgePoster)Frontend
No changes to
Automated code review
Reviewed commit:
2e58beb6a20ca30fd6d3201988e59b294c3e785fVerdict: Requires further work
Resolve the blocking or important findings and investigate failed deterministic checks before merging.
Overall assessment
The PR implements blocked tags with a database migration, CRUD API endpoints, cache integration, and frontend UI updates. However, the implementation calls a missing
invalidatefunction on the blocked cache, which will cause a runtime crash when adding or removing blocked tags.Blocking findings
invalidatefunction in blocked-cache causes TypeError on tag block/unblock (lib/blocked-cache.js; high confidence)routes/tags.js calls
blockedCache.invalidate()inside both the POST and DELETE/api/tags/blockedhandlers. The provided diff for lib/blocked-cache.js only defines and returns aget()function; noinvalidatemethod is defined or exported.Impact: Any attempt to add or remove a blocked tag via the API will throw
TypeError: blockedCache.invalidate is not a function, crashing the request handler and breaking the feature entirely.Suggested fix: Add an
invalidate()function to lib/blocked-cache.js that resetscache = null, and ensure it is exported alongsideget(). Example:function invalidate() { cache = null; } module.exports = { get, invalidate };Important findings
None.
Suggestions
None.
Tests and validation
/usr/bin/node-22[27707]: std::unique_ptr node::WorkerThreadsTaskRunner::DelayedTaskScheduler::Start() at ../../src/node_platform.cc:104
Assertion failed: (0) == (uv_thread_create(t.get(), start_thread, this))
----- Native stack trace -----
1: 0x7f6418427767 node::Assert(node::AssertionInfo const&) [/lib64/libnode.so.127]
2: 0x7f64184be3a2 node::WorkerThreadsTaskRunner::WorkerThreadsTaskRunner(int, node::PlatformDebugLogLevel) [/lib64/libnode.so.127]
3: 0x7f64184d1781 node::NodePlatform::NodePlatform(int, v8::TracingController*, v8::PageAllocator*) [/lib64/libnode.so.127]
4: 0x7f64183f016b [/lib64/libnode.so.127]
5: 0x7f64183f153c node::Start(int, char**) [/lib64/libnode.so.127]
6: 0x7f641800a681 [/lib64/libc.so.6]
7: 0x7f641800a798 __libc_start_main [/lib64/libc.so.6]
8: 0x559c3997e035 _start [/usr/bin/node-22]
`
Questions
archiveMediaWhereinside thepurgeBlockedTagtransaction handle errors gracefully, or will a single archive failure roll back the entire batch of media deletions?invalidatefunction intentionally omitted from this diff, or was it missed during implementation? If already present in the full file, please confirm its export path.Review limitations
Diff coverage
db.js: reviewed — included in a context-limited batchlib/blocked-cache.js: reviewed — included in a context-limited batchpublic/style.css: reviewed — included in a context-limited batchpublic/views/settings.js: reviewed — included in a context-limited batchpublic/views/tags.js: reviewed — included in a context-limited batchroutes/blocked.js: reviewed — included in a context-limited batchroutes/tags.js: reviewed — included in a context-limited batchReview metadata
Addressed all review findings in
3368f53:DOM event listener leak — Moved the
document.addEventListener('click', ...)outsiderenderBlockedTagsand attached it once during initial setup. A module-level_btAddRowreference tracks the current add-row element across re-renders.Silent fallback to purgePoster — Added an explicit guard that logs an error and
continues for unknownitem.typevalues, rather than silently falling through topurgePoster.Identical icon strings — Changed the unblock icon from
⊘to✕so the blocked and unblocked states are visually distinct (thetitleattribute was already correct).Unit test failure — The CI test runner crashed with an assertion in
node::WorkerThreadsTaskRunner, which is a known Node platform threading issue in the sandboxed CI environment. The same tests pass locally (all 14 tests, including the new blocked tag purge flow).Addressed the empty-IN-list finding in
d8c4f94:SQL syntax error on empty purge preview query — Refactored the
GET /api/blocked/purgequery to build clause fragments dynamically. Each non-empty list (subs/users/tags) pushes its ownIN (...)clause; when all are empty the query short-circuits to0without running any SQL. This avoids generatingIN ()orAND ( OR ), both of which are syntax errors in SQLite.Addressed round-2 findings in
77fccce:Event listener accumulation — Moved
_btAddRowto module level and added a_btListenerAttachedguard flag so thedocument.addEventListeneronly registers once regardless of how many timesrenderSettingsis called.source = 'auto'restriction — RemovedAND t.source = 'auto'from both the purge preview query andpurgeBlockedTag. Blocking a tag now catches all content with that tag, whether auto-tagged or manually tagged. This is the more intuitive behavior.Addressed round-3 suggestions in
e51803e:Race condition in block/unblock — Made the onclick handler
asyncandawaits the API call before callingrenderTagGrid(). Error recovery now uses proper try/catch with symmetric set manipulation instead of the previous optimistic-then-revert pattern.Redundant DB query for blocked tags — Extended
blocked-cache.jsto include atagsarray alongsideusersandsubs. BothGET /blocked/purgeandPOST /blocked/purgenow read tags from the cache. The cache is invalidated after POST and DELETE mutations to/api/tags/blocked.Addressed round-4 suggestions in
2e58beb:Unhandled promise rejections & optimistic UI in Settings — Added try/catch to both the Add button handler and the Unblock button handler in the blocked tags subsection. On failure,
renderBlockedTags()is called to sync the UI back to server state.Repeated gallery folders query — Introduced a lazy module-level cache (
galleryFolders()function) that queriesgallery_foldersonce on first use, then returns the cached result. All three purge functions (purgeSubreddit,purgeBlockedTag,purgePoster) share the same cached result instead of each running the query on every invocation.The
invalidatefunction already exists inlib/blocked-cache.js:21-23and is exported on line 25 — this is a false positive in the review. The file was extended ine51803eto add both thetagscache entry and theinvalidateexport thatroutes/tags.jscalls.