Extract shared service layer for sandbox, API key, and template operations

Moves business logic from API handlers into internal/service/ so that
both the REST API and the upcoming dashboard can share the same operations
without duplicating code. API handlers now delegate to the service layer
and only handle HTTP-specific concerns (request parsing, response formatting).
This commit is contained in:
2026-03-16 04:13:11 +06:00
parent 931b7d54b3
commit f38d5812d1
8 changed files with 389 additions and 239 deletions

View File

@ -3,10 +3,12 @@ package api
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net"
"net/http"
"strings"
"time"
"connectrpc.com/connect"
@ -68,6 +70,30 @@ func decodeJSON(r *http.Request, v any) error {
return json.NewDecoder(r.Body).Decode(v)
}
// serviceErrToHTTP maps a service-layer error to an HTTP status, code, and message.
// It inspects the underlying Connect RPC error if present, otherwise returns 500.
func serviceErrToHTTP(err error) (int, string, string) {
msg := err.Error()
// Check for Connect RPC errors wrapped by the service layer.
var connectErr *connect.Error
if errors.As(err, &connectErr) {
return agentErrToHTTP(connectErr)
}
// Map well-known service error patterns.
switch {
case strings.Contains(msg, "not found"):
return http.StatusNotFound, "not_found", msg
case strings.Contains(msg, "not running"), strings.Contains(msg, "not paused"):
return http.StatusConflict, "invalid_state", msg
case strings.Contains(msg, "invalid"):
return http.StatusBadRequest, "invalid_request", msg
default:
return http.StatusInternalServerError, "internal_error", msg
}
}
type statusWriter struct {
http.ResponseWriter
status int