mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
IsSuperAdmin folded the case of the home org and fell back to the owner claim, the org of the application that minted the token, and the money bits were granted on X-User-Owner == admin. Cloud writes X-User-Owner for an admin-org application and an admin-org person's API key as well as for a person, so either could mint money and edit the catalog here while being nobody in the rest of the same process. Every gate now reads X-User-IsAdmin, which the edge strips and writes from authz.Claims.Sudo alone; the standalone EdgeAuth decides it with that predicate over the verified token's signed membership set. X-User-IsAdmin no longer doubles as the org-admin bit, which is X-User-IsOrgAdmin. No machine is SuperAdmin. Global admin is SuperAdmin wherever it named this predicate.
61 lines
1.9 KiB
Go
61 lines
1.9 KiB
Go
package metrics
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/zap-proto/zip"
|
|
|
|
"github.com/hanzoai/commerce/middleware"
|
|
"github.com/hanzoai/commerce/util/permission"
|
|
)
|
|
|
|
// Route registers the SaaS-operations god-view. Like api/costs this is a PLATFORM
|
|
// aggregate, so on top of the route-level token gate the handler ALSO enforces
|
|
// middleware.RequirePlatformAdmin — the route-level TokenRequired(permission.Admin)
|
|
// is a NO-OP on the IAM path (it admits any IAM-authenticated request without
|
|
// checking the Admin bit), so the in-handler gate is the real boundary.
|
|
//
|
|
// External (gateway) path: GET /v1/commerce/metrics/saas. Internal (this bundle):
|
|
// GET /v1/metrics/saas — where the cloud admin proxy and the console's
|
|
// SuperAdmin-gated commerce proxy reach it with the service token.
|
|
func Route(r *zip.Group, args ...zip.Handler) {
|
|
adminRequired := middleware.TokenRequired(permission.Admin)
|
|
|
|
api := r.Group("metrics")
|
|
api.Use(adminRequired)
|
|
|
|
api.Raw(http.MethodGet, "/saas", GetSaaS)
|
|
}
|
|
|
|
// GetSaaS returns the whole-business SaaS snapshot — revenue (MRR/ARR + new/churn),
|
|
// subscription mix, metered usage / LLM-obs, and top customers — from ONE cross-org
|
|
// walk of commerce's subscription + usage ledgers.
|
|
//
|
|
// GET /v1/commerce/metrics/saas?window=30d&limit=20
|
|
//
|
|
// window: 7d | 30d | 90d | mtd | all (default 30d) — bounds usage + new/churn.
|
|
// limit: cap for the top-orgs / customers lists (default 20, max 200).
|
|
func GetSaaS(c *zip.Ctx) error {
|
|
if !middleware.RequirePlatformAdmin(c) {
|
|
return nil
|
|
}
|
|
return c.JSON(200, rollup(options{
|
|
window: c.Query("window"),
|
|
limit: parseLimit(c.Query("limit")),
|
|
test: c.Query("test") == "true",
|
|
}))
|
|
}
|
|
|
|
// parseLimit clamps the top-N cap to [1,200], defaulting to defaultLimit.
|
|
func parseLimit(s string) int {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil || n <= 0 {
|
|
return defaultLimit
|
|
}
|
|
if n > 200 {
|
|
return 200
|
|
}
|
|
return n
|
|
}
|