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