From ceb2b4f161ae437d304c251d5b30c82786d18820 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 2 Jun 2026 22:36:14 +0200 Subject: [PATCH] docs(api/v2): keep registrar godoc attached; clarify registry concurrency + ordering - Move each resource file's init() below its RegisterXRoutes func so the func doc comment stays attached (it was documenting init()). - Note AddRouteRegistrar is init-only and not concurrency-safe. - Reword RegisterAll: registrar order is unspecified and irrelevant. --- pkg/routes/api/v2/admin_projects.go | 4 ++-- pkg/routes/api/v2/avatar.go | 4 ++-- pkg/routes/api/v2/avatar_upload.go | 4 ++-- pkg/routes/api/v2/labels.go | 4 ++-- pkg/routes/api/v2/project_views.go | 4 ++-- pkg/routes/api/v2/registry.go | 12 +++++++++--- pkg/routes/api/v2/task_duplicate.go | 5 +++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/pkg/routes/api/v2/admin_projects.go b/pkg/routes/api/v2/admin_projects.go index 1fd52af63..2203ab01d 100644 --- a/pkg/routes/api/v2/admin_projects.go +++ b/pkg/routes/api/v2/admin_projects.go @@ -31,8 +31,6 @@ 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"} @@ -47,6 +45,8 @@ func RegisterAdminProjectRoutes(api huma.API) { }, adminProjectsList) } +func init() { AddRouteRegistrar(RegisterAdminProjectRoutes) } + func adminProjectsList(ctx context.Context, in *ListParams) (*adminProjectListBody, error) { a, err := authFromCtx(ctx) if err != nil { diff --git a/pkg/routes/api/v2/avatar.go b/pkg/routes/api/v2/avatar.go index f8bf6326c..a26c68e56 100644 --- a/pkg/routes/api/v2/avatar.go +++ b/pkg/routes/api/v2/avatar.go @@ -39,8 +39,6 @@ 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{ @@ -68,6 +66,8 @@ func RegisterAvatarRoutes(api huma.API) { }, avatarGet) } +func init() { AddRouteRegistrar(RegisterAvatarRoutes) } + func avatarGet(ctx context.Context, in *avatarInput) (*avatarResponse, error) { // Authenticated but no per-user check — any authenticated caller may view any // avatar (matching v1); authFromCtx just surfaces a clean 401 if auth is missing. diff --git a/pkg/routes/api/v2/avatar_upload.go b/pkg/routes/api/v2/avatar_upload.go index e2112f001..e8ca6e024 100644 --- a/pkg/routes/api/v2/avatar_upload.go +++ b/pkg/routes/api/v2/avatar_upload.go @@ -41,8 +41,6 @@ type avatarUploadBody struct { Body *models.Message } -func init() { AddRouteRegistrar(RegisterAvatarUploadRoutes) } - func RegisterAvatarUploadRoutes(api huma.API) { tags := []string{"user"} @@ -60,6 +58,8 @@ func RegisterAvatarUploadRoutes(api huma.API) { }, avatarUpload) } +func init() { AddRouteRegistrar(RegisterAvatarUploadRoutes) } + func avatarUpload(ctx context.Context, in *avatarUploadInput) (*avatarUploadBody, error) { a, err := authFromCtx(ctx) if err != nil { diff --git a/pkg/routes/api/v2/labels.go b/pkg/routes/api/v2/labels.go index ca37af903..fcf0a9303 100644 --- a/pkg/routes/api/v2/labels.go +++ b/pkg/routes/api/v2/labels.go @@ -35,8 +35,6 @@ 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"} @@ -87,6 +85,8 @@ func RegisterLabelRoutes(api huma.API) { }, labelsDelete) } +func init() { AddRouteRegistrar(RegisterLabelRoutes) } + func labelsList(ctx context.Context, in *ListParams) (*labelListBody, error) { a, err := authFromCtx(ctx) if err != nil { diff --git a/pkg/routes/api/v2/project_views.go b/pkg/routes/api/v2/project_views.go index c88bbc28a..20d1b23ba 100644 --- a/pkg/routes/api/v2/project_views.go +++ b/pkg/routes/api/v2/project_views.go @@ -34,8 +34,6 @@ 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. @@ -88,6 +86,8 @@ func RegisterProjectViewRoutes(api huma.API) { }, projectViewsDelete) } +func init() { AddRouteRegistrar(RegisterProjectViewRoutes) } + func projectViewsList(ctx context.Context, in *struct { ProjectID int64 `path:"project"` ListParams diff --git a/pkg/routes/api/v2/registry.go b/pkg/routes/api/v2/registry.go index 907f2ea22..ab186045e 100644 --- a/pkg/routes/api/v2/registry.go +++ b/pkg/routes/api/v2/registry.go @@ -23,14 +23,20 @@ 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. +// +// It mutates the package-level routeRegistrars slice without synchronization, so +// it is NOT safe for concurrent use. Call it only during package initialization +// (from an init() func), never at runtime. 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. +// enables AutoPatch. The order in which registrars run is unspecified (Go does +// not guarantee a stable init order across files) and does not matter: each +// resource registers a distinct set of routes. AutoPatch runs last — inside +// RegisterAll, after every registrar — so it can synthesise PATCH counterparts +// for all GET + PUT pairs. func RegisterAll(api huma.API) { for _, r := range routeRegistrars { r(api) diff --git a/pkg/routes/api/v2/task_duplicate.go b/pkg/routes/api/v2/task_duplicate.go index 83d3209b0..170cba857 100644 --- a/pkg/routes/api/v2/task_duplicate.go +++ b/pkg/routes/api/v2/task_duplicate.go @@ -27,8 +27,7 @@ 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. @@ -45,6 +44,8 @@ func RegisterTaskDuplicateRoutes(api huma.API) { }, tasksDuplicate) } +func init() { AddRouteRegistrar(RegisterTaskDuplicateRoutes) } + func tasksDuplicate(ctx context.Context, in *struct { TaskID int64 `path:"projecttask" doc:"The numeric id of the task to duplicate."` }) (*singleBody[models.TaskDuplicate], error) {