CUB-127: implement Control Center CRUD API in Go
Some checks failed
Dev Build / build-test (pull_request) Failing after 11m6s
Dev Build / build-test (push) Successful in 1m54s

This commit is contained in:
2026-05-06 17:29:44 -04:00
parent ab19a7ccde
commit cce3e061a7
16 changed files with 1523 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
package handler
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/go-playground/validator/v10"
)
// ─── Pagination ────────────────────────────────────────────────────────────────
const (
defaultPage = 1
defaultPageSize = 20
maxPageSize = 100
)
// parsePagination extracts page and pageSize query params from the request.
func parsePagination(r *http.Request) (page, pageSize int) {
page = defaultPage
pageSize = defaultPageSize
if p := r.URL.Query().Get("page"); p != "" {
if n, err := strconv.Atoi(p); err == nil && n > 0 {
page = n
}
}
if ps := r.URL.Query().Get("pageSize"); ps != "" {
if n, err := strconv.Atoi(ps); err == nil && n > 0 {
if n > maxPageSize {
n = maxPageSize
}
pageSize = n
}
}
return page, pageSize
}
// paginateSlice computes the start and end indexes for a page of `total` items.
// Bounds are clamped to [0, total].
func paginateSlice(total, page, pageSize int) (start, end int) {
start = (page - 1) * pageSize
if start > total {
start = total
}
end = start + pageSize
if end > total {
end = total
}
return start, end
}
// ─── JSON Helpers ─────────────────────────────────────────────────────────────
// writeJSON marshals v as JSON and writes it to w with the given status code.
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if v == nil {
return
}
if err := json.NewEncoder(w).Encode(v); err != nil {
http.Error(w, `{"error":"internal encoding error"}`, http.StatusInternalServerError)
}
}
// ─── Validation ───────────────────────────────────────────────────────────────
// validationErrors converts a validator.ValidationErrors into a string map
// suitable for the ErrorResponse details field.
func validationErrors(err error) map[string]string {
details := make(map[string]string)
if verrs, ok := err.(validator.ValidationErrors); ok {
for _, fe := range verrs {
field := strings.ToLower(fe.Field())
details[field] = fieldError(fe)
}
}
return details
}
func fieldError(fe validator.FieldError) string {
switch fe.Tag() {
case "required":
return "this field is required"
case "min":
return fmt.Sprintf("must be at least %s characters", fe.Param())
case "max":
return fmt.Sprintf("must be at most %s characters", fe.Param())
default:
return fmt.Sprintf("failed validation: %s", fe.Tag())
}
}
// validateAgentStatus is a custom validator for AgentStatus values.
func validateAgentStatus(fl validator.FieldLevel) bool {
if status, ok := fl.Field().Interface().(interface {
IsValid() bool
}); ok {
return status.IsValid()
}
return false
}