feat: gamemaster

This commit is contained in:
Ilia Denisov
2026-05-03 07:59:03 +02:00
committed by GitHub
parent a7cee15115
commit 3e2622757e
229 changed files with 41521 additions and 1098 deletions
@@ -0,0 +1,127 @@
package ports
import (
"context"
"fmt"
"strings"
"time"
"galaxy/gamemaster/internal/domain/engineversion"
)
//go:generate go run go.uber.org/mock/mockgen -destination=../adapters/mocks/mock_engineversionstore.go -package=mocks galaxy/gamemaster/internal/ports EngineVersionStore
// EngineVersionStore stores the engine version registry rows used by
// Game Lobby's start flow and by GM's admin patch and registry CRUD
// surface. Adapters must preserve domain semantics:
//
// - Get returns engineversion.ErrNotFound when no row exists for
// version.
// - List with a nil status filter returns every row; with a non-nil
// filter, only rows whose status matches are returned.
// - Insert installs a fresh row and returns engineversion.ErrConflict
// when a row with the same `version` already exists. Adapters
// surface PostgreSQL unique violations through that sentinel so
// the service layer maps them to a `conflict` REST envelope.
// - Update applies a partial update; only fields whose pointer is
// non-nil are mutated. The `updated_at` column is always refreshed
// from input.Now.
// - Deprecate sets `status=deprecated` for an existing version with
// `updated_at = now`. It returns engineversion.ErrNotFound when no
// row exists. The call is idempotent: deprecating an already
// deprecated row succeeds with no further mutation.
// - Delete removes the row identified by version. Returns
// engineversion.ErrNotFound when no row matches. The service layer
// gates Delete behind an explicit IsReferencedByActiveRuntime probe
// so referenced rows surface engineversion.ErrInUse before the
// adapter is touched; adapters do not enforce that guard themselves.
// - IsReferencedByActiveRuntime reports whether any non-finished
// `runtime_records` row currently references the version through
// `current_engine_version`.
type EngineVersionStore interface {
// Get returns the row identified by version. Returns
// engineversion.ErrNotFound when no row exists.
Get(ctx context.Context, version string) (engineversion.EngineVersion, error)
// List returns every row whose status matches statusFilter when
// non-nil, or every row when nil. The order is adapter-defined.
List(ctx context.Context, statusFilter *engineversion.Status) ([]engineversion.EngineVersion, error)
// Insert installs record into the registry.
Insert(ctx context.Context, record engineversion.EngineVersion) error
// Update applies a partial update to the row identified by
// input.Version. Only fields whose pointer is non-nil are mutated.
// Returns engineversion.ErrNotFound when no row exists.
Update(ctx context.Context, input UpdateEngineVersionInput) error
// Deprecate sets `status=deprecated` for version and refreshes
// `updated_at` from now. Returns engineversion.ErrNotFound when no
// row exists. Calling Deprecate on an already-deprecated row
// succeeds with no mutation (idempotent).
Deprecate(ctx context.Context, version string, now time.Time) error
// Delete removes the row identified by version. Returns
// engineversion.ErrNotFound when no row matches. Adapters do not
// inspect runtime references; the service layer probes
// IsReferencedByActiveRuntime first and surfaces
// engineversion.ErrInUse independently.
Delete(ctx context.Context, version string) error
// IsReferencedByActiveRuntime reports whether any non-finished
// runtime row currently references version through
// `current_engine_version`. Used by the registry hard-delete path
// to surface engineversion.ErrInUse.
IsReferencedByActiveRuntime(ctx context.Context, version string) (bool, error)
}
// UpdateEngineVersionInput stores the arguments required to PATCH one
// engine version row. Pointer fields communicate «leave alone» (nil)
// vs. «write the value» (non-nil). At least one optional field must be
// set; otherwise the call is a no-op and Validate rejects it.
type UpdateEngineVersionInput struct {
// Version identifies the row to mutate.
Version string
// ImageRef is the new image reference. Nil leaves the column
// unchanged; non-nil must be non-empty.
ImageRef *string
// Options is the new options document (raw JSON). Nil leaves the
// column unchanged; non-nil writes the value verbatim.
Options *[]byte
// Status is the new status. Nil leaves the column unchanged;
// non-nil must be a known status.
Status *engineversion.Status
// Now stores the wall-clock used to refresh the `updated_at`
// column on every successful update.
Now time.Time
}
// Validate reports whether input contains a structurally valid PATCH
// request. Adapters call Validate before touching the store.
func (input UpdateEngineVersionInput) Validate() error {
if strings.TrimSpace(input.Version) == "" {
return fmt.Errorf("update engine version: version must not be empty")
}
if input.ImageRef == nil && input.Options == nil && input.Status == nil {
return fmt.Errorf("update engine version: at least one field must be set")
}
if input.ImageRef != nil && strings.TrimSpace(*input.ImageRef) == "" {
return fmt.Errorf(
"update engine version: image ref must not be empty when set",
)
}
if input.Status != nil && !input.Status.IsKnown() {
return fmt.Errorf(
"update engine version: status %q is unsupported",
*input.Status,
)
}
if input.Now.IsZero() {
return fmt.Errorf("update engine version: now must not be zero")
}
return nil
}