mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-24 21:52:16 +00:00
feat: Add platform handlers for services, monitoring, deployments, plugins, automations, chaos experiments, and configuration management
- Add ServicesHandler with List/Get/Restart/Start/Stop/Scale/Logs/Health/PodStatus/Events methods and Kubernetes service integration - Add MonitoringHandler with Metrics/Alerts/Health/Logs methods and Prometheus service integration - Add DeploymentsHandler with Status/Start/Rollback/History methods and deployment service integration - Add PluginsHandler with List
This commit is contained in:
@@ -0,0 +1,708 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/monitoring"
|
||||
"github.com/nutcas3/telecom-platform/apps/api-server/internal/services"
|
||||
)
|
||||
|
||||
// ErrorResponse is a shared error envelope used across platform handlers.
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func badRequest(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "Invalid request", Details: err.Error(), Code: "BAD_REQUEST"})
|
||||
}
|
||||
|
||||
func serverError(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "Internal error", Details: err.Error(), Code: "INTERNAL_ERROR"})
|
||||
}
|
||||
|
||||
func notFound(c *gin.Context, err error) {
|
||||
c.JSON(http.StatusNotFound, ErrorResponse{Error: "Not found", Details: err.Error(), Code: "NOT_FOUND"})
|
||||
}
|
||||
|
||||
// parseUint helper
|
||||
func parseUintParam(c *gin.Context, name string) (uint, bool) {
|
||||
v, err := strconv.ParseUint(c.Param(name), 10, 32)
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return 0, false
|
||||
}
|
||||
return uint(v), true
|
||||
}
|
||||
|
||||
// parseBoolQuery returns *bool for optional tri-state filters.
|
||||
func parseBoolQuery(c *gin.Context, key string) *bool {
|
||||
raw := c.Query(key)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
b, err := strconv.ParseBool(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
type ServicesHandler struct {
|
||||
k8s *services.KubernetesService
|
||||
}
|
||||
|
||||
func NewServicesHandler(k8s *services.KubernetesService) *ServicesHandler {
|
||||
return &ServicesHandler{k8s: k8s}
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) List(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
deps, err := h.k8s.ListDeployments(c.Request.Context())
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"services": deps, "total": len(deps), "namespace": h.k8s.Namespace()})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Get(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
d, err := h.k8s.GetDeployment(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, d)
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Restart(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
if err := h.k8s.RestartDeployment(c.Request.Context(), c.Param("id")); err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "restart triggered", "service": c.Param("id")})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Start(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
// Starting = scaling to >=1 replica (default 1).
|
||||
if err := h.k8s.ScaleDeployment(c.Request.Context(), c.Param("id"), 1); err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "service started", "service": c.Param("id")})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Stop(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
if err := h.k8s.ScaleDeployment(c.Request.Context(), c.Param("id"), 0); err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "service stopped", "service": c.Param("id")})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Scale(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Replicas int32 `json:"replicas" binding:"required,min=0,max=100"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := h.k8s.ScaleDeployment(c.Request.Context(), c.Param("id"), req.Replicas); err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "scaled", "service": c.Param("id"), "replicas": req.Replicas})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Logs(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
lines, _ := strconv.Atoi(c.DefaultQuery("lines", "100"))
|
||||
logs, err := h.k8s.PodLogs(c.Request.Context(), c.Param("id"), lines)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"service": c.Param("id"), "logs": logs})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Health(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
d, err := h.k8s.GetDeployment(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
status := "healthy"
|
||||
if d.ReadyReplicas < d.Replicas {
|
||||
status = "degraded"
|
||||
}
|
||||
if d.ReadyReplicas == 0 {
|
||||
status = "unhealthy"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"service": c.Param("id"),
|
||||
"status": status,
|
||||
"replicas_desired": d.Replicas,
|
||||
"replicas_ready": d.ReadyReplicas,
|
||||
"replicas_available": d.AvailableReplicas,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) PodStatus(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
status, err := h.k8s.GetPodStatus(c.Request.Context(), c.Param("id"))
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
func (h *ServicesHandler) Events(c *gin.Context) {
|
||||
if h.k8s == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, ErrorResponse{Error: "Kubernetes not configured", Code: "K8S_UNAVAILABLE"})
|
||||
return
|
||||
}
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
||||
events, err := h.k8s.GetEvents(c.Request.Context(), c.Param("id"), limit)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"events": events, "total": len(events)})
|
||||
}
|
||||
|
||||
type MonitoringHandler struct {
|
||||
prom *monitoring.PrometheusService
|
||||
}
|
||||
|
||||
func NewMonitoringHandler(prom *monitoring.PrometheusService) *MonitoringHandler {
|
||||
return &MonitoringHandler{prom: prom}
|
||||
}
|
||||
|
||||
func (h *MonitoringHandler) Metrics(c *gin.Context) {
|
||||
if h.prom == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "monitoring service not available"})
|
||||
return
|
||||
}
|
||||
q := c.DefaultQuery("query", "up")
|
||||
samples, err := h.prom.Query(c.Request.Context(), q)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"query": q, "samples": samples})
|
||||
}
|
||||
|
||||
func (h *MonitoringHandler) Alerts(c *gin.Context) {
|
||||
if h.prom == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "monitoring service not available"})
|
||||
return
|
||||
}
|
||||
alerts, err := h.prom.Alerts(c.Request.Context())
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"alerts": alerts})
|
||||
}
|
||||
|
||||
func (h *MonitoringHandler) Health(c *gin.Context) {
|
||||
if h.prom == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"status": "degraded",
|
||||
"prometheus_available": false,
|
||||
"error": "monitoring service not available",
|
||||
})
|
||||
return
|
||||
}
|
||||
available := h.prom.Available(c.Request.Context())
|
||||
status := "healthy"
|
||||
if !available {
|
||||
status = "degraded"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": status,
|
||||
"prometheus_available": available,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *MonitoringHandler) Logs(c *gin.Context) {
|
||||
// Logs are aggregated in Elasticsearch; expose a stub that points callers
|
||||
// to /v1/services/:id/logs for live pod logs.
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Use /v1/services/:id/logs for live pod logs; aggregated logs are in Kibana",
|
||||
})
|
||||
}
|
||||
|
||||
type DeploymentsHandler struct {
|
||||
svc *services.DeploymentService
|
||||
}
|
||||
|
||||
func NewDeploymentsHandler(svc *services.DeploymentService) *DeploymentsHandler {
|
||||
return &DeploymentsHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *DeploymentsHandler) Status(c *gin.Context) {
|
||||
f := services.DeploymentFilter{
|
||||
Service: c.Query("service"),
|
||||
Environment: c.Query("environment"),
|
||||
Status: "in_progress",
|
||||
Limit: 50,
|
||||
}
|
||||
items, total, err := h.svc.List(c.Request.Context(), f)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"deployments": items, "total": total})
|
||||
}
|
||||
|
||||
func (h *DeploymentsHandler) Start(c *gin.Context) {
|
||||
var in services.StartDeploymentInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
d, err := h.svc.Start(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, d)
|
||||
}
|
||||
|
||||
func (h *DeploymentsHandler) Rollback(c *gin.Context) {
|
||||
var in services.RollbackInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
d, err := h.svc.Rollback(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, d)
|
||||
}
|
||||
|
||||
func (h *DeploymentsHandler) History(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
f := services.DeploymentFilter{
|
||||
Service: c.Query("service"),
|
||||
Environment: c.Query("environment"),
|
||||
Status: c.Query("status"),
|
||||
Limit: pageSize,
|
||||
Offset: (page - 1) * pageSize,
|
||||
}
|
||||
items, total, err := h.svc.List(c.Request.Context(), f)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"deployments": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
type PluginsHandler struct {
|
||||
svc *services.PluginService
|
||||
}
|
||||
|
||||
func NewPluginsHandler(svc *services.PluginService) *PluginsHandler {
|
||||
return &PluginsHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) List(c *gin.Context) {
|
||||
items, err := h.svc.List(c.Request.Context(), services.PluginFilter{
|
||||
Enabled: parseBoolQuery(c, "enabled"),
|
||||
Type: c.Query("type"),
|
||||
Category: c.Query("category"),
|
||||
})
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"plugins": items, "total": len(items)})
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) Get(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
plugin, err := h.svc.Get(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, plugin)
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) Install(c *gin.Context) {
|
||||
var in services.InstallPluginInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
p, err := h.svc.Install(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, p)
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) Uninstall(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.svc.Uninstall(c.Request.Context(), id); err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "plugin uninstalled", "id": id})
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) Enable(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p, err := h.svc.SetEnabled(c.Request.Context(), id, true)
|
||||
if err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, p)
|
||||
}
|
||||
|
||||
func (h *PluginsHandler) Disable(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p, err := h.svc.SetEnabled(c.Request.Context(), id, false)
|
||||
if err != nil {
|
||||
notFound(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, p)
|
||||
}
|
||||
|
||||
type AutomationHandler struct {
|
||||
svc *services.AutomationService
|
||||
}
|
||||
|
||||
func NewAutomationHandler(svc *services.AutomationService) *AutomationHandler {
|
||||
return &AutomationHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *AutomationHandler) List(c *gin.Context) {
|
||||
items, err := h.svc.List(c.Request.Context(), services.AutomationFilter{
|
||||
Enabled: parseBoolQuery(c, "enabled"),
|
||||
Type: c.Query("type"),
|
||||
})
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"automations": items, "total": len(items)})
|
||||
}
|
||||
|
||||
func (h *AutomationHandler) Create(c *gin.Context) {
|
||||
var in services.CreateAutomationInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
a, err := h.svc.Create(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, a)
|
||||
}
|
||||
|
||||
func (h *AutomationHandler) Run(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r, err := h.svc.Run(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, r)
|
||||
}
|
||||
|
||||
func (h *AutomationHandler) Schedule(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var in services.ScheduleInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
a, err := h.svc.UpdateSchedule(c.Request.Context(), id, in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, a)
|
||||
}
|
||||
|
||||
func (h *AutomationHandler) Logs(c *gin.Context) {
|
||||
var automationID uint
|
||||
if raw := c.Query("automation_id"); raw != "" {
|
||||
v, err := strconv.ParseUint(raw, 10, 32)
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
automationID = uint(v)
|
||||
}
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "100"))
|
||||
runs, err := h.svc.ListRuns(c.Request.Context(), automationID, limit)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"runs": runs, "total": len(runs)})
|
||||
}
|
||||
|
||||
type ConfigHandler struct {
|
||||
svc *services.ConfigStoreService
|
||||
}
|
||||
|
||||
func NewConfigHandler(svc *services.ConfigStoreService) *ConfigHandler {
|
||||
return &ConfigHandler{svc: svc}
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) Get(c *gin.Context) {
|
||||
entries, err := h.svc.List(c.Request.Context(), c.Query("section"))
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"entries": entries, "total": len(entries)})
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) Update(c *gin.Context) {
|
||||
var in services.UpsertConfigInput
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
entry, err := h.svc.Upsert(c.Request.Context(), in)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, entry)
|
||||
}
|
||||
|
||||
func (h *ConfigHandler) Validate(c *gin.Context) {
|
||||
res, err := h.svc.Validate(c.Request.Context(), c.Query("section"))
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
type ChaosHandler struct {
|
||||
svc *services.ChaosService
|
||||
}
|
||||
|
||||
func NewChaosHandler(svc *services.ChaosService) *ChaosHandler {
|
||||
return &ChaosHandler{svc: svc}
|
||||
}
|
||||
|
||||
type runExperimentRequest struct {
|
||||
Type services.ExperimentType `json:"type" binding:"required"`
|
||||
Target string `json:"target" binding:"required"`
|
||||
DurationMS int64 `json:"duration_ms"`
|
||||
Probability float64 `json:"probability"`
|
||||
Amount int `json:"amount"`
|
||||
}
|
||||
|
||||
func (h *ChaosHandler) Run(c *gin.Context) {
|
||||
var req runExperimentRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
cfg := services.ExperimentConfig{
|
||||
Duration: millisToDuration(req.DurationMS),
|
||||
Probability: req.Probability,
|
||||
Amount: req.Amount,
|
||||
Target: req.Target,
|
||||
}
|
||||
exp, err := h.svc.RunExperiment(c.Request.Context(), req.Type, cfg)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, exp)
|
||||
}
|
||||
|
||||
func (h *ChaosHandler) List(c *gin.Context) {
|
||||
active := h.svc.GetActiveExperiments()
|
||||
history, _ := h.svc.GetExperimentHistory()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"active": active,
|
||||
"history": history,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ChaosHandler) Status(c *gin.Context) {
|
||||
id := c.Query("id")
|
||||
if id == "" {
|
||||
// No id: return summary of active experiments
|
||||
active := h.svc.GetActiveExperiments()
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"active_count": len(active),
|
||||
"active": active,
|
||||
})
|
||||
return
|
||||
}
|
||||
exp, ok := h.svc.GetExperimentStatus(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, ErrorResponse{Error: "experiment not found", Code: "NOT_FOUND"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, exp)
|
||||
}
|
||||
|
||||
type BillingHandler struct {
|
||||
invoice *services.InvoiceService
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBillingHandler(invoice *services.InvoiceService, db *gorm.DB) *BillingHandler {
|
||||
return &BillingHandler{invoice: invoice, db: db}
|
||||
}
|
||||
|
||||
type generateInvoiceRequest struct {
|
||||
SubscriberID uint `json:"subscriber_id" binding:"required"`
|
||||
BillingPeriod string `json:"billing_period" binding:"required"` // e.g. "2024-01"
|
||||
}
|
||||
|
||||
func (h *BillingHandler) GenerateInvoice(c *gin.Context) {
|
||||
var req generateInvoiceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
inv, err := h.invoice.GenerateMonthlyInvoice(c.Request.Context(), req.SubscriberID, req.BillingPeriod)
|
||||
if err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, inv)
|
||||
}
|
||||
|
||||
func (h *BillingHandler) ListInvoices(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
q := h.db.WithContext(c.Request.Context()).Model(&models.Invoice{}).Preload("LineItems")
|
||||
if s := c.Query("status"); s != "" {
|
||||
q = q.Where("status = ?", s)
|
||||
}
|
||||
if sid := c.Query("subscriber_id"); sid != "" {
|
||||
q = q.Where("subscriber_id = ?", sid)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
var items []models.Invoice
|
||||
if err := q.Order("created_at DESC").Limit(pageSize).Offset((page - 1) * pageSize).Find(&items).Error; err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"invoices": items, "total": total, "page": page, "page_size": pageSize})
|
||||
}
|
||||
|
||||
func (h *BillingHandler) ListPayments(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
q := h.db.WithContext(c.Request.Context()).Model(&models.Transaction{})
|
||||
if s := c.Query("status"); s != "" {
|
||||
q = q.Where("status = ?", s)
|
||||
}
|
||||
if sid := c.Query("subscriber_id"); sid != "" {
|
||||
q = q.Where("subscriber_id = ?", sid)
|
||||
}
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
var items []models.Transaction
|
||||
if err := q.Order("created_at DESC").Limit(pageSize).Offset((page - 1) * pageSize).Find(&items).Error; err != nil {
|
||||
serverError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"payments": items, "total": total, "page": page, "page_size": pageSize})
|
||||
}
|
||||
Reference in New Issue
Block a user