Files
kandev/apps/backend/internal/system/toolretention/handler.go
T
2026-09-15 09:38:21 +01:00

129 lines
3.4 KiB
Go

package toolretention
import (
"context"
"encoding/json"
"io"
"net/http"
"github.com/gin-gonic/gin"
)
// HTTPService is the retention operations exposed by the settings endpoints.
type HTTPService interface {
Get(context.Context) (Status, error)
Save(context.Context, Update) (Status, error)
Analyze(context.Context, Age) (string, error)
Run(context.Context, int64) (string, error)
Cancel(context.Context, string) (Status, error)
}
// RegisterRoutes keeps status readable while all work requires admin admission.
func RegisterRoutes(read, admin *gin.RouterGroup, service HTTPService) {
const base = "/database/tool-payload-retention"
read.GET(base, func(c *gin.Context) {
status, err := service.Get(c.Request.Context())
respond(c, http.StatusOK, status, err)
})
admin.PUT(base, handleSave(service))
admin.POST(base+"/analyze", func(c *gin.Context) {
var req struct {
Age *Age `json:"age"`
}
if !decodeBody(c, &req) {
return
}
if req.Age == nil {
badRequest(c)
return
}
id, err := service.Analyze(c.Request.Context(), *req.Age)
respond(c, http.StatusAccepted, gin.H{"operation_id": id}, err)
})
admin.POST(base+"/run", func(c *gin.Context) {
var req struct {
Revision *int64 `json:"revision"`
}
if !decodeBody(c, &req) {
return
}
if req.Revision == nil || *req.Revision < 0 {
badRequest(c)
return
}
id, err := service.Run(c.Request.Context(), *req.Revision)
respond(c, http.StatusAccepted, gin.H{"operation_id": id}, err)
})
admin.POST(base+"/cancel", func(c *gin.Context) {
var req struct {
OperationID string `json:"operation_id"`
}
if !decodeBody(c, &req) {
return
}
if req.OperationID == "" {
badRequest(c)
return
}
status, err := service.Cancel(c.Request.Context(), req.OperationID)
respond(c, http.StatusOK, status, err)
})
}
func handleSave(service HTTPService) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
Enabled *bool `json:"enabled"`
Age *Age `json:"age"`
Revision *int64 `json:"revision"`
BackupChoice string `json:"backup_choice,omitempty"`
}
if !decodeBody(c, &req) {
return
}
if req.Enabled == nil || req.Age == nil || req.Revision == nil || *req.Revision < 0 {
badRequest(c)
return
}
status, err := service.Save(c.Request.Context(), Update{Enabled: *req.Enabled, Age: *req.Age, Revision: *req.Revision, BackupChoice: req.BackupChoice})
respond(c, http.StatusOK, status, err)
}
}
func decodeBody(c *gin.Context, target any) bool {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20)
decoder := json.NewDecoder(c.Request.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(target); err != nil {
badRequest(c)
return false
}
if err := decoder.Decode(new(any)); err != io.EOF {
badRequest(c)
return false
}
return true
}
func badRequest(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"code": "invalid_request"}) }
func respond(c *gin.Context, status int, result any, err error) {
if err == nil {
c.JSON(status, result)
return
}
code := err.Error()
switch code {
case "invalid_age", "invalid_backup_choice", "backup_choice_required":
status = http.StatusBadRequest
case "conflict", "busy", "preparation_required":
status = http.StatusConflict
case "unsupported":
status = http.StatusNotImplemented
default:
status = http.StatusInternalServerError
code = "internal_error"
}
c.JSON(status, gin.H{"code": code})
}