Files
Achmad a7df9ffc6c Slice 2: sandbox, template, environment, route CRUD
- store: add tables and CRUD for sandboxes (with services), templates
  (with services, clone-into-sandbox), environments (named key/value
  sets), and routes (per-sandbox <service>_url overrides).
- api: split into one file per resource. handleSandboxes/handleSandboxByID
  covers CRUD + 'clone from template' + 'deploy one service in a sandbox'
  (which merges the sandbox's env into the request, picks the port,
  and dispatches the deploy frame to the right node).
  handleTemplates/handleTemplateByID, handleEnvironments/handleEnvironmentByID,
  handlePushRoutes cover the rest. The control plane's repo->node
  resolution still lives in resolveNode (api-gateway -> gateway,
  everything else -> micro).
2026-06-24 03:59:02 +00:00

106 lines
2.7 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strings"
"github.com/sdp/control-plane/internal/store"
)
type templateReq struct {
Name string `json:"name"`
GatewayBranch string `json:"gatewayBranch"`
Services []store.TemplateService `json:"services"`
}
func (s *Server) handleTemplates(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
ts, err := s.st.ListTemplates()
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
if ts == nil {
ts = []store.Template{}
}
writeJSON(w, http.StatusOK, ts)
case http.MethodPost:
var body templateReq
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if body.Name == "" {
writeErr(w, http.StatusBadRequest, "name required")
return
}
t := store.Template{
ID: newID(),
Name: body.Name,
GatewayBranch: body.GatewayBranch,
Services: body.Services,
}
if err := s.st.CreateTemplate(t); err != nil {
writeErr(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, t)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
func (s *Server) handleTemplateByID(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/api/templates/")
if id == "" || strings.Contains(id, "/") {
http.Error(w, "not found", http.StatusNotFound)
return
}
switch r.Method {
case http.MethodGet:
t, err := s.st.GetTemplate(id)
if err == store.ErrNotFound {
http.Error(w, "not found", http.StatusNotFound)
return
}
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, t)
case http.MethodPut:
var body templateReq
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
t := store.Template{
ID: id,
Name: body.Name,
GatewayBranch: body.GatewayBranch,
Services: body.Services,
}
if err := s.st.UpdateTemplate(t); err == store.ErrNotFound {
http.Error(w, "not found", http.StatusNotFound)
return
} else if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, t)
case http.MethodDelete:
if err := s.st.DeleteTemplate(id); err == store.ErrNotFound {
http.Error(w, "not found", http.StatusNotFound)
return
} else if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}