From 11b179b91f0547a4c738d26fec80525086f41318 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 24 Oct 2026 14:33:37 -0400 Subject: [PATCH] =?UTF-8?q?fix(admin):=20hash-conflict=20routes=20served?= =?UTF-8?q?=20at=20sane=20path=20=E2=80=94=20resolve=20buttons=20were=20de?= =?UTF-8?q?ad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin group already sits under the /api prefix, so registering "/api/admin/hash-conflicts" produced /api/api/admin/hash-conflicts while the hash-conflicts page's htmx buttons posted to /api/admin/hash-conflicts/:id/ resolve — a 404 that htmx swallows silently (no swap, no error), so duplicate- content resolution has been broken since the feature shipped (03cb4c78). Verified live: sane path 401 anon -> 200 with admin token; doubled path 404. Caller audit found no other consumers (Android app, koplugin, web reader, tests, Bruno: no references). Docs already describe the sane path and are now correct without edits. --- internal/router/router.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/router/router.go b/internal/router/router.go index af747730..07e1e57c 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -409,9 +409,13 @@ func RegisterRoutes(cfg *Config) *handlers.Handler { admin := protected.Group("", handlers.AdminMiddleware) registerScannerRoutes(protected, admin, scannerHandler) - // Hash conflict routes (admin only) - admin.GET("/api/admin/hash-conflicts", cfg.HashConflictsHandler.ListHashConflicts) - admin.POST("/api/admin/hash-conflicts/:id/resolve", cfg.HashConflictsHandler.ResolveHashConflict) + // Hash conflict routes (admin only). NOTE: `admin` already sits under the + // /api group (protected), so these paths must NOT start with /api — + // they used to, producing /api/api/admin/hash-conflicts while the admin + // page's htmx buttons posted to /api/admin/hash-conflicts/:id/resolve, + // which 404'd silently (htmx swaps nothing on error responses). + admin.GET("/admin/hash-conflicts", cfg.HashConflictsHandler.ListHashConflicts) + admin.POST("/admin/hash-conflicts/:id/resolve", cfg.HashConflictsHandler.ResolveHashConflict) return scannerHandler }