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

83 lines
2.2 KiB
Go

package store
import (
"database/sql"
)
// Route is one "<service>_url" line attached to a sandbox. The agent
// pushes the whole set to the gateway by replacing any matching lines
// in the gateway's config.php.
type Route struct {
ID string `json:"id"`
SandboxID string `json:"sandboxId"`
Key string `json:"key"`
Value string `json:"value"`
TargetOCP bool `json:"targetOcp"`
}
// SetRoutes atomically replaces the routing table for a sandbox. The
// gateway agent's "push" frame is the whole set.
func (s *Store) SetRoutes(sandboxID string, routes []Route) error {
tx, err := s.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(`DELETE FROM routes WHERE sandbox_id=?`, sandboxID); err != nil {
return err
}
for i := range routes {
r := routes[i]
r.SandboxID = sandboxID
if r.ID == "" {
r.ID = sandboxID + "-" + r.Key
}
if _, err := tx.Exec(
`INSERT INTO routes(id, sandbox_id, key, value, target_ocp) VALUES(?,?,?,?,?)`,
r.ID, r.SandboxID, r.Key, r.Value, boolInt(r.TargetOCP)); err != nil {
return err
}
}
return tx.Commit()
}
// ListRoutes returns the routes for one sandbox.
func (s *Store) ListRoutes(sandboxID string) ([]Route, error) {
rows, err := s.db.Query(
`SELECT id, sandbox_id, key, COALESCE(value,''), target_ocp
FROM routes WHERE sandbox_id=? ORDER BY key`, sandboxID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Route
for rows.Next() {
var r Route
var targetOCP int
if err := rows.Scan(&r.ID, &r.SandboxID, &r.Key, &r.Value, &targetOCP); err != nil {
return nil, err
}
r.TargetOCP = targetOCP == 1
out = append(out, r)
}
return out, rows.Err()
}
// GetRoute returns one route by sandbox+key, or ErrNotFound.
func (s *Store) GetRoute(sandboxID, key string) (*Route, error) {
row := s.db.QueryRow(
`SELECT id, sandbox_id, key, COALESCE(value,''), target_ocp
FROM routes WHERE sandbox_id=? AND key=?`, sandboxID, key)
var r Route
var targetOCP int
err := row.Scan(&r.ID, &r.SandboxID, &r.Key, &r.Value, &targetOCP)
if err == sql.ErrNoRows {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
r.TargetOCP = targetOCP == 1
return &r, nil
}