diff --git a/pkg/routes/api/v2/admin_projects.go b/pkg/routes/api/v2/admin_projects.go index 8e7b51d3d..1fd52af63 100644 --- a/pkg/routes/api/v2/admin_projects.go +++ b/pkg/routes/api/v2/admin_projects.go @@ -31,6 +31,8 @@ type adminProjectListBody struct { Body Paginated[*models.Project] } +func init() { AddRouteRegistrar(RegisterAdminProjectRoutes) } + // Permissions are enforced by the gateV2AdminRoutes path middleware, not per-handler. func RegisterAdminProjectRoutes(api huma.API) { tags := []string{"admin"} diff --git a/pkg/routes/api/v2/avatar.go b/pkg/routes/api/v2/avatar.go index c2bd4c9bd..f8bf6326c 100644 --- a/pkg/routes/api/v2/avatar.go +++ b/pkg/routes/api/v2/avatar.go @@ -39,6 +39,8 @@ type avatarInput struct { Size int64 `query:"size" default:"250" minimum:"1" doc:"Desired avatar edge length in pixels. Clamped to the server's configured maximum if larger; providers that render fixed-size images may ignore it."` } +func init() { AddRouteRegistrar(RegisterAvatarRoutes) } + // RegisterAvatarRoutes wires the avatar binary endpoint onto the Huma API. func RegisterAvatarRoutes(api huma.API) { Register(api, huma.Operation{ diff --git a/pkg/routes/api/v2/avatar_upload.go b/pkg/routes/api/v2/avatar_upload.go index 6794cdb81..e2112f001 100644 --- a/pkg/routes/api/v2/avatar_upload.go +++ b/pkg/routes/api/v2/avatar_upload.go @@ -41,6 +41,8 @@ type avatarUploadBody struct { Body *models.Message } +func init() { AddRouteRegistrar(RegisterAvatarUploadRoutes) } + func RegisterAvatarUploadRoutes(api huma.API) { tags := []string{"user"} diff --git a/pkg/routes/api/v2/labels.go b/pkg/routes/api/v2/labels.go index 541ab1bb2..ca37af903 100644 --- a/pkg/routes/api/v2/labels.go +++ b/pkg/routes/api/v2/labels.go @@ -35,6 +35,8 @@ type labelListBody struct { Body Paginated[*models.LabelWithTaskID] } +func init() { AddRouteRegistrar(RegisterLabelRoutes) } + // RegisterLabelRoutes wires Label CRUD onto the Huma API. func RegisterLabelRoutes(api huma.API) { tags := []string{"labels"} diff --git a/pkg/routes/api/v2/project_views.go b/pkg/routes/api/v2/project_views.go index 907d305c9..c88bbc28a 100644 --- a/pkg/routes/api/v2/project_views.go +++ b/pkg/routes/api/v2/project_views.go @@ -34,6 +34,8 @@ type projectViewListBody struct { Body Paginated[*models.ProjectView] } +func init() { AddRouteRegistrar(RegisterProjectViewRoutes) } + // RegisterProjectViewRoutes wires the nested ProjectView CRUD onto the Huma API. // Every operation binds two path params: {project} → ProjectID and {view} → ID. // This is the reference shape every nested sub-resource copies. diff --git a/pkg/routes/api/v2/registry.go b/pkg/routes/api/v2/registry.go new file mode 100644 index 000000000..907f2ea22 --- /dev/null +++ b/pkg/routes/api/v2/registry.go @@ -0,0 +1,39 @@ +// 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 + +import "github.com/danielgtaylor/huma/v2" + +var routeRegistrars []func(huma.API) + +// AddRouteRegistrar records a resource's route-registration function. Each +// resource file calls this from an init() so new resources never touch the +// central wiring. +func AddRouteRegistrar(f func(huma.API)) { + routeRegistrars = append(routeRegistrars, f) +} + +// RegisterAll runs every registrar collected via AddRouteRegistrar, then +// enables AutoPatch. Registrars run in init() order (filename order across the +// package); the order they register routes in is irrelevant. AutoPatch runs +// last so it can synthesise PATCH counterparts for all GET + PUT pairs. +func RegisterAll(api huma.API) { + for _, r := range routeRegistrars { + r(api) + } + EnableAutoPatch(api) +} diff --git a/pkg/routes/api/v2/task_duplicate.go b/pkg/routes/api/v2/task_duplicate.go index 3340994bd..83d3209b0 100644 --- a/pkg/routes/api/v2/task_duplicate.go +++ b/pkg/routes/api/v2/task_duplicate.go @@ -27,6 +27,8 @@ import ( ) // RegisterTaskDuplicateRoutes wires the task-duplicate action onto the Huma API. +func init() { AddRouteRegistrar(RegisterTaskDuplicateRoutes) } + // TaskDuplicate is a CRUDable Create, so the handler reuses handler.DoCreate // (its CanCreate enforces read-source + write-project); the only custom part is // taking TaskID from the path rather than a request body. diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 3fca382a1..159994724 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -415,17 +415,8 @@ func registerAPIRoutesV2(e *echo.Echo, a *echo.Group) { a.GET("/docs", apiv2.ScalarUI) a.GET("/docs/scalar.standalone.js", apiv2.ScalarJS) - // Resource registrations. - apiv2.RegisterLabelRoutes(api) - apiv2.RegisterTaskDuplicateRoutes(api) - apiv2.RegisterProjectViewRoutes(api) - apiv2.RegisterAdminProjectRoutes(api) - apiv2.RegisterAvatarRoutes(api) - apiv2.RegisterAvatarUploadRoutes(api) - - // AutoPatch must run AFTER all GET/PUT pairs are registered so it can - // synthesize their PATCH counterparts. - apiv2.EnableAutoPatch(api) + // Resources self-register via init(); RegisterAll runs them all + AutoPatch. + apiv2.RegisterAll(api) } func registerAPIRoutes(a *echo.Group) {