chore: establish local platform baseline

This commit is contained in:
Codex Local
2026-08-26 06:41:54 +08:00
commit 29bdf4900f
575 changed files with 95644 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
package httpapi
import (
"errors"
"net/http"
"time"
"data-terminal-cloud/internal/billing"
"data-terminal-cloud/internal/concurrency"
"data-terminal-cloud/internal/idempotency"
)
func CreateOpsPriceBookHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, key, ok := opsCreateHeaders(w, r)
if !ok {
return
}
var body billing.CreatePriceBookInput
if !decodeWebJSON(w, r, &body, "费用单价版本参数不正确") {
return
}
body.TenantID = tenantID
item, created, err := service.CreatePriceBook(r.Context(), principal, key, RequestID(r.Context()), body)
if err != nil {
writeBillingError(w, r, err)
return
}
status := http.StatusCreated
if !created {
status = http.StatusOK
w.Header().Set("Idempotency-Replayed", "true")
}
w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion))
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, status, item)
})
}
func ListOpsPriceBooksHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, ok := opsTenantHeader(w, r)
if !ok {
return
}
items, err := service.ListPriceBooks(r.Context(), principal, tenantID)
if err != nil {
writeBillingError(w, r, err)
return
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
})
}
func PublishOpsPriceBookHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := resourcePrincipal(w, r, "id")
if !ok {
return
}
tenantID, ok := opsTenantHeader(w, r)
if !ok {
return
}
expected, err := concurrency.ParseIfMatch(r)
if err != nil {
writeETagError(w, r, err)
return
}
var body struct {
Reason string `json:"reason"`
}
if !decodeWebJSON(w, r, &body, "费用版本发布参数不正确") {
return
}
item, err := service.PublishPriceBook(r.Context(), principal, tenantID, r.PathValue("id"), RequestID(r.Context()), body.Reason, expected)
if err != nil {
writeBillingError(w, r, err)
return
}
w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion))
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, item)
})
}
func CreateOpsQuotaPolicyHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, key, ok := opsCreateHeaders(w, r)
if !ok {
return
}
var body billing.QuotaInput
if !decodeWebJSON(w, r, &body, "用量额度策略参数不正确") {
return
}
item, created, err := service.CreateQuotaPolicy(r.Context(), principal, tenantID, key, RequestID(r.Context()), body)
if err != nil {
writeBillingError(w, r, err)
return
}
status := http.StatusCreated
if !created {
status = http.StatusOK
w.Header().Set("Idempotency-Replayed", "true")
}
w.Header().Set("ETag", concurrency.FormatETag(item.RowVersion))
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, status, item)
})
}
func ListOpsQuotaPoliciesHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, ok := opsTenantHeader(w, r)
if !ok {
return
}
items, err := service.ListQuotaPolicies(r.Context(), principal, tenantID)
if err != nil {
writeBillingError(w, r, err)
return
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
})
}
func GetOpsCostSummaryHandler(service *billing.Service) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
principal, ok := webPrincipal(w, r)
if !ok {
return
}
tenantID, ok := opsTenantHeader(w, r)
if !ok {
return
}
from, fromErr := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
to, toErr := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
if fromErr != nil || toErr != nil {
writeValidation(w, r, "费用汇总时间范围不正确", FieldError{Field: "from/to", Message: "必须是RFC3339时间且范围不超过366天"})
return
}
item, err := service.CostSummary(r.Context(), principal, tenantID, from, to)
if err != nil {
writeBillingError(w, r, err)
return
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, item)
})
}
func writeBillingError(w http.ResponseWriter, r *http.Request, err error) {
switch {
case errors.Is(err, billing.ErrInvalid):
writeValidation(w, r, "用量、费用或额度参数不正确", FieldError{Field: "billing", Message: "字段、单位、时区、金额或范围不符合约束"})
case errors.Is(err, billing.ErrAccessDenied):
WriteError(w, r, http.StatusForbidden, "OPS_ACCESS_DENIED", "当前账号没有所需的内部运营权限")
case errors.Is(err, billing.ErrNotFound):
WriteError(w, r, http.StatusNotFound, "RESOURCE_NOT_FOUND", "费用或额度资源不存在")
case errors.Is(err, billing.ErrVersionConflict):
WriteError(w, r, http.StatusPreconditionFailed, "VERSION_CONFLICT", "资源版本已变化,请重新读取后再操作")
case errors.Is(err, billing.ErrSelfApproval):
WriteError(w, r, http.StatusConflict, "SELF_APPROVAL_FORBIDDEN", "费用版本创建人不能审批自己的变更")
case errors.Is(err, billing.ErrStateConflict):
WriteError(w, r, http.StatusConflict, "BILLING_STATE_CONFLICT", "当前状态不允许执行该操作")
case errors.Is(err, idempotency.ErrKeyConflict):
WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已被不同请求使用")
case errors.Is(err, idempotency.ErrInProgress):
WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_IN_PROGRESS", "相同请求正在处理中")
default:
WriteError(w, r, http.StatusInternalServerError, "INTERNAL_ERROR", "用量费用处理失败")
}
}