From d1243aceacedeac83037ce9e4f23a0b96fba61e3 Mon Sep 17 00:00:00 2001 From: dtoro Date: Mon, 13 Jul 2026 10:29:42 +0200 Subject: [PATCH] fix(web): decode percent-encoded slugs in the knowledge content route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chi.URLParam returns the raw, still-encoded path segment — unlike the OpenAPI-generated routes, which decode via runtime.BindStyledParameterWithOptions before the handler sees them. Slugs like "document:containers/101-jellyfin" (encoded by the frontend's encodeURIComponent) were arriving undecoded and matching no row. Found via a standalone chi repro, not by patching the live deploy checkout. Co-Authored-By: Claude Sonnet 5 --- internal/httpapi/knowledge.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/httpapi/knowledge.go b/internal/httpapi/knowledge.go index 8bd2911..c990609 100644 --- a/internal/httpapi/knowledge.go +++ b/internal/httpapi/knowledge.go @@ -5,6 +5,7 @@ import ( "encoding/json" "log/slog" "net/http" + "net/url" "strconv" "github.com/dtoro/oikos/internal/httpapi/gen" @@ -116,12 +117,21 @@ func (s *Server) serveRecentKnowledge(w http.ResponseWriter, req *http.Request) // panel needs the entity's own full content when it IS a knowledge entity. func (s *Server) serveKnowledgeContent(w http.ResponseWriter, req *http.Request) { ctx := req.Context() - idOrSlug := chi.URLParam(req, "id") + // chi.URLParam returns the raw, still-percent-encoded segment (unlike + // the OpenAPI-generated routes, which decode via + // runtime.BindStyledParameterWithOptions before reaching the handler) — + // slugs like "document:containers/101-jellyfin" arrive as + // "document%3Acontainers%2F101-jellyfin" and must be unescaped here. + idOrSlug, err := url.PathUnescape(chi.URLParam(req, "id")) + if err != nil { + writeProblem(w, req, http.StatusBadRequest, "invalid id", err.Error()) + return + } var title, content, source string var tags []string var updatedAt string - err := s.pool.QueryRow(ctx, ` + err = s.pool.QueryRow(ctx, ` SELECT ke.title, ke.content, COALESCE(ke.source,''), ke.tags, ke.updated_at::text FROM knowledge_entities ke JOIN entities e ON e.id = ke.entity_id