From 5fefa885777e9cd3eb6ba0b33673c1eb686a909c Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 21 Apr 2026 12:58:19 +0200 Subject: [PATCH] feat(routes): scaffold /api/v2 Echo group --- pkg/routes/api/v2/huma.go | 44 +++++++++++++++++++++++++++++++++++++++ pkg/routes/routes.go | 19 ++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 pkg/routes/api/v2/huma.go diff --git a/pkg/routes/api/v2/huma.go b/pkg/routes/api/v2/huma.go new file mode 100644 index 000000000..30824c384 --- /dev/null +++ b/pkg/routes/api/v2/huma.go @@ -0,0 +1,44 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package apiv2 wires Huma onto the /api/v2 Echo group. +package apiv2 + +import ( + "code.vikunja.io/api/pkg/modules/humaecho5" + "code.vikunja.io/api/pkg/version" + + "github.com/danielgtaylor/huma/v2" + "github.com/labstack/echo/v5" +) + +// GroupPrefix is the URL prefix the Echo group for /api/v2 is mounted at. +const GroupPrefix = "/api/v2" + +// NewAPI mounts Huma on the /api/v2 group. Per-resource Register* calls +// live in sibling files. +func NewAPI(e *echo.Echo, g *echo.Group) huma.API { + cfg := huma.DefaultConfig("Vikunja API", version.Version) + cfg.OpenAPIPath = "/openapi" + // Huma's built-in docs would load from unpkg.com — we serve Scalar locally instead. + cfg.DocsPath = "" + // Partial-update permissive: non-pointer fields do not become required + // at the schema layer (legacy /v1 handlers are permissive by convention; + // govalidator enforces real field-level rules later). + cfg.FieldsOptionalByDefault = true + + return humaecho5.NewWithGroup(e, g, GroupPrefix, cfg) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index e72d9ec61..8dd390e0c 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -80,6 +80,7 @@ import ( "code.vikunja.io/api/pkg/plugins" apiv1 "code.vikunja.io/api/pkg/routes/api/v1" adminapi "code.vikunja.io/api/pkg/routes/api/v1/admin" + apiv2 "code.vikunja.io/api/pkg/routes/api/v2" "code.vikunja.io/api/pkg/routes/caldav" "code.vikunja.io/api/pkg/routes/feeds" "code.vikunja.io/api/pkg/version" @@ -303,6 +304,10 @@ func RegisterRoutes(e *echo.Echo) { a := e.Group("/api/v1") registerAPIRoutes(a) + // /api/v2 — Huma-backed API, scaffolded alongside /api/v1. + a2 := e.Group("/api/v2") + registerAPIRoutesV2(e, a2) + // Collect routes for API token permissions // In Echo v5, we collect routes after registration using e.Router().Routes() collectRoutesForAPITokens(e) @@ -325,6 +330,11 @@ var unauthenticatedAPIPaths = map[string]bool{ "/api/v1/docs/redoc.standalone.js": true, "/api/v1/metrics": true, "/api/v1/oauth/token": true, + + "/api/v2/openapi.json": true, + "/api/v2/openapi.yaml": true, + "/api/v2/docs": true, + "/api/v2/docs/scalar.standalone.js": true, } // collectRoutesForAPITokens collects all routes for API token permission checking. @@ -334,7 +344,7 @@ func collectRoutesForAPITokens(e *echo.Echo) { log.Debugf("Collecting %d routes for API token usage", len(routeList)) for _, route := range routeList { // Only process API routes - if !strings.HasPrefix(route.Path, "/api/v1") { + if !strings.HasPrefix(route.Path, "/api/v1") && !strings.HasPrefix(route.Path, "/api/v2") { continue } @@ -345,6 +355,13 @@ func collectRoutesForAPITokens(e *echo.Echo) { } } +// registerAPIRoutesV2 wires the /api/v2 Echo group. Huma and per-resource +// route registrations land here in later sub-phases. +func registerAPIRoutesV2(e *echo.Echo, a *echo.Group) { + _ = apiv2.NewAPI(e, a) + // Resource registrations go here in later sub-phases. +} + func registerAPIRoutes(a *echo.Group) { // Prevent browsers from caching API responses. Without an explicit