P1.1: Registry package with full CRUD and tests

SQLite schema (services, components, ports, volumes, cmd, events),
migrations, and complete CRUD operations. 7 tests covering:
idempotent migration, service CRUD, duplicate name rejection,
component CRUD with ports/volumes/cmd, composite PK enforcement,
cascade delete, and event insert/query/count/prune.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:18:35 -07:00
parent 3f23b14ef4
commit 6122123064
8 changed files with 963 additions and 4 deletions

View File

@@ -0,0 +1,94 @@
package registry
import (
"database/sql"
"fmt"
"time"
)
// Service represents a service in the registry.
type Service struct {
Name string
Active bool
CreatedAt time.Time
UpdatedAt time.Time
}
// CreateService creates a new service in the registry.
func CreateService(db *sql.DB, name string, active bool) error {
_, err := db.Exec(
"INSERT INTO services (name, active) VALUES (?, ?)",
name, active,
)
if err != nil {
return fmt.Errorf("create service %q: %w", name, err)
}
return nil
}
// GetService retrieves a service by name.
func GetService(db *sql.DB, name string) (*Service, error) {
s := &Service{}
var createdAt, updatedAt string
err := db.QueryRow(
"SELECT name, active, created_at, updated_at FROM services WHERE name = ?",
name,
).Scan(&s.Name, &s.Active, &createdAt, &updatedAt)
if err != nil {
return nil, fmt.Errorf("get service %q: %w", name, err)
}
s.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
s.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
return s, nil
}
// ListServices returns all services.
func ListServices(db *sql.DB) ([]Service, error) {
rows, err := db.Query("SELECT name, active, created_at, updated_at FROM services ORDER BY name")
if err != nil {
return nil, fmt.Errorf("list services: %w", err)
}
defer func() { _ = rows.Close() }()
var services []Service
for rows.Next() {
var s Service
var createdAt, updatedAt string
if err := rows.Scan(&s.Name, &s.Active, &createdAt, &updatedAt); err != nil {
return nil, fmt.Errorf("scan service: %w", err)
}
s.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
s.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
services = append(services, s)
}
return services, rows.Err()
}
// UpdateServiceActive updates a service's active flag.
func UpdateServiceActive(db *sql.DB, name string, active bool) error {
res, err := db.Exec(
"UPDATE services SET active = ?, updated_at = datetime('now') WHERE name = ?",
active, name,
)
if err != nil {
return fmt.Errorf("update service %q: %w", name, err)
}
n, _ := res.RowsAffected()
if n == 0 {
return fmt.Errorf("update service %q: %w", name, sql.ErrNoRows)
}
return nil
}
// DeleteService removes a service and all its components (via CASCADE).
func DeleteService(db *sql.DB, name string) error {
res, err := db.Exec("DELETE FROM services WHERE name = ?", name)
if err != nil {
return fmt.Errorf("delete service %q: %w", name, err)
}
n, _ := res.RowsAffected()
if n == 0 {
return fmt.Errorf("delete service %q: %w", name, sql.ErrNoRows)
}
return nil
}