diff --git a/pkg/routes/api/v2/huma.go b/pkg/routes/api/v2/huma.go index 9d2e44556..720a5929d 100644 --- a/pkg/routes/api/v2/huma.go +++ b/pkg/routes/api/v2/huma.go @@ -20,7 +20,9 @@ package apiv2 import ( "context" "net/http" + "strings" + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/modules/humaecho5" "code.vikunja.io/api/pkg/version" @@ -65,6 +67,20 @@ func NewAPI(e *echo.Echo, g *echo.Group) huma.API { {"JWTKeyAuth": {}}, {"APITokenAuth": {}}, } + // The relative entry MUST stay at index 0. Huma's SchemaLinkTransformer + // reads Servers[0] in three places (getAPIPrefix, addSchemaField, the + // runtime Transform fallback). With a path-bearing absolute URL at + // index 0, the runtime fallback concatenates that URL onto a ref that + // already includes /api/v2, producing a double-prefixed $schema link + // like https://host/api/v2/api/v2/schemas/Label.json. A relative URL + // at index 0 keeps the prefix in the transformer's bookkeeping while + // the absolute URL at index 1 advertises the deployment URL to SDK + // generators and docs UIs. + servers := []*huma.Server{{URL: GroupPrefix}} + if publicURL := strings.TrimRight(config.ServicePublicURL.GetString(), "/"); publicURL != "" { + servers = append(servers, &huma.Server{URL: publicURL + GroupPrefix}) + } + oapi.Servers = servers return api } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index ceb82d20b..c81dcef1f 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -342,6 +342,7 @@ var unauthenticatedAPIPaths = map[string]bool{ "/api/v2/openapi-3.0.yaml": true, "/api/v2/docs": true, "/api/v2/docs/scalar.standalone.js": true, + "/api/v2/schemas/:schema": true, } // collectRoutesForAPITokens collects all routes for API token permission checking.