feat(dashboard-api): add admin endpoint for listing tiers - #3399
Conversation
Admin clients that render or edit a team's tier need the set of tiers to
choose from, and public.tiers was not reachable over HTTP.
GET /admin/tiers is admin-token authenticated and returns {id, name}
ordered by name.
Regenerating api.gen.go also picks up oapi-codegen v2.7.1, which the
checked-in file had drifted behind.
Calling the handler directly can't catch a route registered without security, which is the failure mode that matters for /admin/*. Exercise the generated router behind the spec-driven validator instead, including the status promotion main.go relies on to turn the validator's 400 into the 401 the authenticator wrote.
PR SummaryLow Risk Overview Reviewed by Cursor Bugbot for commit ddbc4c4. Bugbot is set up for automated code reviews on this repo. Configure here. |
❌ 5 Tests Failed:
View the top 3 failed test(s) by shortest run time
View the full list of 1 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
LGTM. Straightforward, well-tested addition that mirrors existing admin endpoint patterns exactly.
What was reviewed: the new GET /admin/tiers route wiring (spec, generated router, handler), the ListTiers sqlc query, and the auth/route-level tests. Confirmed the endpoint is gated by AdminApiKeyAuth (not the team ApiKeyAuth) via route_conflict_test.go, and that the response only projects id/name from public.tiers, excluding resource-limit columns.
Extended reasoning...
Overview
The PR adds a single new read-only admin endpoint, GET /admin/tiers, following the exact pattern of existing /admin/* routes in packages/dashboard-api. Changes span: the OpenAPI spec (new path + schemas), the generated api.gen.go (routing plumbing plus an unrelated oapi-codegen v2.7.0→v2.7.1 version bump that the author explains is just catching this package up to what every other generated file already uses), a new sqlc query (ListTiers) reading id, name from public.tiers ordered by name, and a new handler (admin_tiers.go) that maps the rows into the response DTO.
Security risks
The endpoint is read-only, requires AdminApiKeyAuth (X-Admin-Token), and deliberately excludes tier resource-limit fields (max_vcpu, max_ram_mb, etc.) from the response, projecting only id and name. route_conflict_test.go explicitly asserts the route requires the admin scheme and does not accept a team API key, guarding against the route being reachable without the intended auth. No user input is taken (no path/query params), so there's no injection surface. I don't see any security concerns here.
Level of scrutiny
This is a low-risk, additive, read-only admin endpoint that follows an established pattern almost line-for-line (compare to admin_auth_provider_profiles.go and its route tests). It doesn't touch production request-serving paths for sandboxes/orchestration, doesn't modify existing behavior, and the generated-code diff is mechanical. A light-touch review is appropriate here.
Other factors
Test coverage is strong for the size of the change: DB-backed ordering/empty-list tests, a route-level test that exercises the real request validator + authenticator (specifically to catch the "route registered without security" failure mode), and a spec-level assertion that the route declares the right security scheme. The author also states they verified the auth tests aren't vacuous by temporarily removing security: and confirming the tests fail. No outstanding reviewer comments exist on the PR (only a Cursor bot summary placeholder). The bug hunting system found no issues, and my own reading of the diff didn't surface anything additional.
Problem
public.tiersis not reachable over HTTP. Admin clients that display a team's tier, filter teams by tier, or offer a tier change have no way to obtain the set of tiers to choose from — every other admin surface is already served bydashboard-api, so this is the one gap that forces a direct database connection.Nothing in the repo queried
public.tiersbefore this change: the existing "tier" queries all read the denormalizedteams.tiertext column or theteam_limitsview, so there was no model or query to extend.Changes
GET /admin/tiersinspec/openapi-dashboard.yml, secured withAdminApiKeyAuth(X-Admin-Token) like the other/admin/*routes. Returns{"tiers": [{"id", "name"}]}ordered by name.ListTierssqlc query underpackages/db/pkg/dashboard/sql_queries/tiers/, and the handler ininternal/handlers/admin_tiers.go.idandname. Tiers carry resource limits (max_vcpu,max_ram_mb,concurrent_instances, disk entitlements, …) that a tier picker has no business seeing, and keeping them out means adding a column doesn't silently widen the payload.[], notnull.Regenerating
api.gen.goalso moves it from oapi-codegen v2.7.0 to v2.7.1, which accounts for most of the diff in that file. The version is whatgo.modalready pins and what every other package's generated file was built with —packages/dashboard-apihad drifted behind because the rootmake generatetarget doesn't include it.Testing
go test ./packages/dashboard-api/...(testcontainers Postgres), plusgo vetandnpx @redocly/cli lint spec/openapi-dashboard.yml— the only new lint warning isoperation-operationId, which all 26 existing operations also emit.Three layers:
admin_tiers_test.go— DB-backed: ordering by name across seeded and inserted tiers, and the empty-table case serializing as{"tiers":[]}. The fixture clones the migration-seeded tier throughjsonb_populate_recordso adding a column topublic.tiersdoesn't break it.admin_tiers_route_test.go— drives the generated router behind the spec's request validator: rejects a missing and a wrong token with 401, serves the tier list with a valid one. Calling the handler function directly cannot catch a route registered without security, which is the failure mode that matters for/admin/*. This test also has to reproduce the status promotionmain.goperforms, since the validator surfaces auth failures as 400 and the authenticator's 401 has to win.route_conflict_test.go— asserts every admin route declaresAdminApiKeyAuthand does not accept a team API key.I checked the auth tests aren't vacuous by temporarily dropping
security:from the new path and regenerating: both the route test and the spec assertion fail, and the endpoint starts answering unauthenticated requests with 200.