From b3edc85df8ac6f305011f65586d82216b9798dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Barras=20Hampel?= Date: Thu, 2 Jul 2026 01:09:59 +0200 Subject: [PATCH] feat(middleware): add per-org sidebar nav ordering (GetOrgNav/UpdateOrgNav) Client support for the nav-ordering endpoint (PROD-774): GET/PUT /organizations/{org}/nav. Mirrors the OIDCOrganizationSettings pattern (cmd/cycloid/middleware/organization_oidc.go) rather than generating from swagger -- it's a simple two-field GET/PUT pair, and a hand-defined type + GenericRequest is more direct here than swagger-driven codegen. NavItem{Type, Key, Position} and NavConfig{Items} mirror the backend's NavItem/NavConfig models exactly (type: "native"|"plugin_widget", key, position uint32 >= 1). Passing a nil or empty items slice to UpdateOrgNav always sends an explicit [] (never JSON null) -- an empty array is what resets the ordering to defaults per the API contract; omitting the field or sending null is not the same thing. Depends on the backend endpoint existing on develop -- currently missing due to a regression (ENGBE-282), restore PR open at cycloidio/youdeploy-http-api#5977. This CLI change works standalone regardless (it's just a client method), but GetOrgNav/UpdateOrgNav will 404 until that PR merges. Intended consumer: a new cycloid_organization_nav_order resource in terraform-provider-cycloid (TFPRO-55), PR to follow this one. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_014CST9mw3ABSA4aYHNy96EV --- cmd/cycloid/middleware/middleware.go | 4 ++ cmd/cycloid/middleware/organization_nav.go | 64 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 cmd/cycloid/middleware/organization_nav.go diff --git a/cmd/cycloid/middleware/middleware.go b/cmd/cycloid/middleware/middleware.go index 42aa99b9..332645f8 100644 --- a/cmd/cycloid/middleware/middleware.go +++ b/cmd/cycloid/middleware/middleware.go @@ -98,6 +98,10 @@ type Middleware interface { GetOIDCIntegration(org string) (*OIDCIntegration, *http.Response, error) UpdateOIDCIntegration(org string, config map[string]interface{}) (*OIDCIntegration, *http.Response, error) + // organization_nav — per-org sidebar nav ordering + GetOrgNav(org string) (*NavConfig, *http.Response, error) + UpdateOrgNav(org string, items []*NavItem) (*NavConfig, *http.Response, error) + // organizations CreateOrganization(name string) (*models.Organization, *http.Response, error) UpdateOrganization(org, name string) (*models.Organization, *http.Response, error) diff --git a/cmd/cycloid/middleware/organization_nav.go b/cmd/cycloid/middleware/organization_nav.go new file mode 100644 index 00000000..47fbe581 --- /dev/null +++ b/cmd/cycloid/middleware/organization_nav.go @@ -0,0 +1,64 @@ +package middleware + +import ( + "fmt" + "net/http" +) + +// NavItem is a single sidebar entry with an explicit position. +// +// This type lives in the middleware package (not client/models) following the +// same convention as OIDCOrganizationSettings: the endpoint is simple enough +// (two fields, a GET/PUT pair) that a hand-defined type plus GenericRequest is +// more direct than swagger-driven codegen for it. +type NavItem struct { + // Type is either "native" (a built-in section, identified by name, e.g. + // "dashboard") or "plugin_widget" (identified by the widget's plugin ID as + // a string). + Type string `json:"type"` + Key string `json:"key"` + // Position is 1-indexed; positions must be unique within a NavConfig. + Position uint32 `json:"position"` +} + +// NavConfig is the per-organization sidebar nav ordering configuration. +// Items is empty when no ordering has been saved — the console falls back to +// its default ordering in that case. +type NavConfig struct { + Items []*NavItem `json:"items"` +} + +// GetOrgNav returns the org's sidebar nav ordering config. +func (m *middleware) GetOrgNav(org string) (*NavConfig, *http.Response, error) { + var result *NavConfig + resp, err := m.GenericRequest(Request{ + Method: "GET", + Organization: &org, + Route: []string{"organizations", org, "nav"}, + }, &result) + if err != nil { + return nil, resp, fmt.Errorf("failed to get organization nav ordering: %w", err) + } + return result, resp, nil +} + +// UpdateOrgNav creates or replaces the org's sidebar nav ordering config. +// Passing an empty (or nil) items slice resets the ordering to defaults. +func (m *middleware) UpdateOrgNav(org string, items []*NavItem) (*NavConfig, *http.Response, error) { + body := &NavConfig{Items: items} + if body.Items == nil { + body.Items = []*NavItem{} + } + + var result *NavConfig + resp, err := m.GenericRequest(Request{ + Method: "PUT", + Organization: &org, + Route: []string{"organizations", org, "nav"}, + Body: body, + }, &result) + if err != nil { + return nil, resp, fmt.Errorf("failed to update organization nav ordering: %w", err) + } + return result, resp, nil +}