mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
The server moved off /api/vN; the integration suite, handler tests, install.sh's health wait and the docs still called the old tree. Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
158 lines
3.9 KiB
Go
158 lines
3.9 KiB
Go
package impllmpricingrule
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/hanzoai/o11y/pkg/errors"
|
|
"github.com/hanzoai/o11y/pkg/http/binding"
|
|
"github.com/hanzoai/o11y/pkg/http/render"
|
|
"github.com/hanzoai/o11y/pkg/modules/llmpricingrule"
|
|
"github.com/hanzoai/o11y/pkg/types/authtypes"
|
|
"github.com/hanzoai/o11y/pkg/types/coretypes"
|
|
"github.com/hanzoai/o11y/pkg/types/llmpricingruletypes"
|
|
"github.com/hanzoai/o11y/pkg/valuer"
|
|
)
|
|
|
|
const maxLimit = 100
|
|
|
|
type handler struct {
|
|
module llmpricingrule.Module
|
|
}
|
|
|
|
func NewHandler(module llmpricingrule.Module) llmpricingrule.Handler {
|
|
return &handler{module: module}
|
|
}
|
|
|
|
// List handles GET /v1/o11y/llm_pricing_rules.
|
|
func (h *handler) List(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
var q llmpricingruletypes.ListPricingRulesQuery
|
|
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if q.Limit <= 0 {
|
|
q.Limit = 20
|
|
} else if q.Limit > maxLimit {
|
|
q.Limit = maxLimit
|
|
}
|
|
if q.Offset < 0 {
|
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "offset must be a non-negative integer"))
|
|
return
|
|
}
|
|
|
|
rules, total, err := h.module.List(ctx, orgID, q.Offset, q.Limit, q.Search, q.IsOverride)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, llmpricingruletypes.NewGettableLLMPricingRulesFromLLMPricingRules(rules, total, q.Offset, q.Limit))
|
|
}
|
|
|
|
// Get handles GET /v1/o11y/llm_pricing_rules/{id}.
|
|
func (h *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
id, err := ruleIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
rule, err := h.module.Get(ctx, orgID, id)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, rule)
|
|
}
|
|
|
|
func (h *handler) CreateOrUpdate(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
req := new(llmpricingruletypes.UpdatableLLMPricingRules)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = h.module.CreateOrUpdate(ctx, orgID, claims.Email, req.Rules)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// Delete handles DELETE /v1/o11y/llm_pricing_rules/{id}.
|
|
func (h *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
id, err := ruleIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if err := h.module.Delete(ctx, orgID, id); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// ruleIDFromPath extracts and validates the {id} path segment. The read goes
|
|
// through coretypes.Param — the ONE route-value reader — so this handler names
|
|
// the value it needs and not the router that matched it.
|
|
func ruleIDFromPath(r *http.Request) (valuer.UUID, error) {
|
|
id, err := valuer.NewUUID(coretypes.Param(r, "id"))
|
|
if err != nil {
|
|
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, llmpricingruletypes.ErrCodePricingRuleInvalidInput, "id is not a valid uuid")
|
|
}
|
|
return id, nil
|
|
}
|