Files
go-sip/cmd/saas-server/operations.go
T

141 lines
4.6 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"log/slog"
"time"
"git.ipao.vip/rogee/go-sip/internal/configread"
"github.com/gofiber/fiber/v3"
)
var errUnknownResource = errors.New("unknown resource")
func (s *service) replace(resource, id string, raw []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
candidate := s.data
candidate.tasks = make(map[string][]byte, len(s.data.tasks))
for key, value := range s.data.tasks {
candidate.tasks[key] = value
}
raw = bytes.Clone(raw)
switch resource {
case "sip":
if err := validateResource(raw, "sip_config", candidate.dispatcherID); err != nil {
return err
}
candidate.sip = raw
case "providers":
if err := validateResource(raw, "ai_providers", candidate.dispatcherID); err != nil {
return err
}
candidate.providers = raw
case "quota":
if err := validateResource(raw, "tenant_quota", candidate.dispatcherID); err != nil {
return err
}
var quota configread.Quota
if err := json.Unmarshal(raw, &quota); err != nil {
return errors.New("invalid quota")
}
if quota.TenantID != candidate.tenantID {
return errors.New("cannot change tenant identity")
}
candidate.quota = raw
case "task":
if _, ok := candidate.tasks[id]; !ok {
return errUnknownResource
}
if err := validateResource(raw, "task_config", candidate.dispatcherID); err != nil {
return err
}
var task configread.Task
if err := json.Unmarshal(raw, &task); err != nil {
return errors.New("invalid task")
}
if task.TaskID != id || task.TenantID != candidate.tenantID {
return errors.New("cannot change task identity")
}
candidate.tasks[id] = raw
default:
return errUnknownResource
}
if err := candidate.validateReferences(); err != nil {
return err
}
s.data = candidate
slog.Info("Test configuration changed in memory", "resource", resource, "task_id", id, "files_modified", false)
return nil
}
func (s *service) routes() {
update := func(resource string) fiber.Handler {
return func(c fiber.Ctx) error {
if err := s.replace(resource, c.Params("task_id"), c.Body()); err != nil {
status := 400
if errors.Is(err, errUnknownResource) {
status = 404
}
return c.Status(status).JSON(fiber.Map{"error": err.Error()})
}
note := "memory only; send an explicit refresh command to apply"
if resource == "providers" {
note = "memory only; Dispatcher reads providers on startup; no service restart performed"
}
return c.JSON(fiber.Map{"status": "updated", "note": note})
}
}
s.app.Put("/test/config/sip", update("sip"))
s.app.Put("/test/config/providers", update("providers"))
s.app.Put("/test/config/quota", update("quota"))
s.app.Put("/test/config/tasks/:task_id", update("task"))
command := func(kind string) fiber.Handler {
return func(c fiber.Ctx) error {
var input commandInput
dec := json.NewDecoder(bytes.NewReader(c.Body()))
dec.DisallowUnknownFields()
if !json.Valid(c.Body()) || dec.Decode(&input) != nil {
return c.Status(400).JSON(fiber.Map{"error": "invalid command JSON"})
}
// A single command has one purpose; do not silently accept fields from another command.
if (kind == "call.execute" && (input.Action != "" || input.Reason != "" || input.TrunkID != "" || input.ChangeType != "" || input.Revision != nil)) || (kind == "task.control" && (input.Callee != "" || input.TrunkID != "" || input.ChangeType != "" || input.Revision != nil)) || (kind == "sip.config" && (input.TaskID != "" || input.Callee != "" || input.Action != "" || input.Reason != "")) {
return c.Status(400).JSON(fiber.Map{"error": "fields do not belong to this command"})
}
ctx, cancel := context.WithTimeout(c.Context(), 5*time.Second)
defer cancel()
record, err := s.send(ctx, kind, input, time.Now())
if err != nil {
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
}
code := 200
if record.Status != "confirmed" {
code = 503
}
return c.Status(code).JSON(record)
}
}
s.app.Post("/test/calls", command("call.execute"))
s.app.Post("/test/task-controls", command("task.control"))
s.app.Post("/test/sip-refresh", command("sip.config"))
s.app.Get("/test/events", func(c fiber.Ctx) error {
id := c.Query("event_id")
if id == "" {
return c.Status(400).JSON(fiber.Map{"error": "event_id is required"})
}
feedback := s.events(id)
requests := []requestRecord{}
s.eventMu.Lock()
for _, record := range s.requests {
if record.EventID == id {
requests = append(requests, record)
}
}
s.eventMu.Unlock()
return c.JSON(fiber.Map{"requests": requests, "feedback": feedback, "history": "current process only"})
})
}