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

114 lines
2.8 KiB
Go

package store
import (
"database/sql"
"time"
)
// Environment is a named set of env vars that can be attached to a
// deploy, a sandbox service, or a sandbox's gateway.
type Environment struct {
ID string `json:"id"`
Name string `json:"name"`
Values map[string]string `json:"values"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
// CreateEnvironment persists a new env. Caller assigns id and timestamps.
func (s *Store) CreateEnvironment(e Environment) error {
now := time.Now().UnixMilli()
if e.CreatedAt == 0 {
e.CreatedAt = now
}
e.UpdatedAt = now
json, err := marshalJSON(e.Values)
if err != nil {
return err
}
_, err = s.db.Exec(
`INSERT INTO environments(id, name, values_json, created_at, updated_at) VALUES(?,?,?,?,?)`,
e.ID, e.Name, json, e.CreatedAt, e.UpdatedAt)
return err
}
// UpdateEnvironment replaces name + values. ErrNotFound if missing.
func (s *Store) UpdateEnvironment(e Environment) error {
now := time.Now().UnixMilli()
e.UpdatedAt = now
json, err := marshalJSON(e.Values)
if err != nil {
return err
}
res, err := s.db.Exec(
`UPDATE environments SET name=?, values_json=?, updated_at=? WHERE id=?`,
e.Name, json, e.UpdatedAt, e.ID)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return ErrNotFound
}
return nil
}
// GetEnvironment returns one env. ErrNotFound if missing.
func (s *Store) GetEnvironment(id string) (*Environment, error) {
row := s.db.QueryRow(
`SELECT id, name, values_json, created_at, updated_at FROM environments WHERE id=?`, id)
var e Environment
var raw string
err := row.Scan(&e.ID, &e.Name, &raw, &e.CreatedAt, &e.UpdatedAt)
if err == sql.ErrNoRows {
return nil, ErrNotFound
}
if err != nil {
return nil, err
}
v, err := unmarshalJSON(raw)
if err != nil {
return nil, err
}
e.Values = v
return &e, nil
}
// ListEnvironments returns all envs, newest first.
func (s *Store) ListEnvironments() ([]Environment, error) {
rows, err := s.db.Query(
`SELECT id, name, values_json, created_at, updated_at FROM environments ORDER BY created_at DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Environment
for rows.Next() {
var e Environment
var raw string
if err := rows.Scan(&e.ID, &e.Name, &raw, &e.CreatedAt, &e.UpdatedAt); err != nil {
return nil, err
}
v, err := unmarshalJSON(raw)
if err != nil {
return nil, err
}
e.Values = v
out = append(out, e)
}
return out, rows.Err()
}
// DeleteEnvironment removes one env. ErrNotFound if missing.
func (s *Store) DeleteEnvironment(id string) error {
res, err := s.db.Exec(`DELETE FROM environments WHERE id=?`, id)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return ErrNotFound
}
return nil
}