Add history-based state changes and recompute pipeline

This commit is contained in:
2026-02-22 19:52:07 +03:00
parent c84102d2f1
commit ec54d3249e
22 changed files with 4973 additions and 32 deletions

View File

@@ -5,12 +5,14 @@ import (
"strings"
"reanimator/internal/domain"
historysvc "reanimator/internal/history"
"reanimator/internal/repository/registry"
)
type RegistryDependencies struct {
Assets *registry.AssetRepository
Components *registry.ComponentRepository
History *historysvc.Service
}
type registryHandlers struct {
@@ -136,25 +138,61 @@ func (h registryHandlers) handleAssetByID(w http.ResponseWriter, r *http.Request
writeError(w, http.StatusBadRequest, "vendor_serial is required")
return
}
item, err := h.deps.Assets.Update(r.Context(), domain.Asset{
ID: id,
Name: strings.TrimSpace(req.Name),
Vendor: normalizeOptionalString(req.Vendor),
Model: normalizeOptionalString(req.Model),
VendorSerial: strings.TrimSpace(req.VendorSerial),
MachineTag: normalizeOptionalString(req.AssetTag),
})
var item domain.Asset
var err error
if h.deps.History != nil {
patch := []historysvc.PatchOp{
{Op: "replace", Path: "/identity/name", Value: strings.TrimSpace(req.Name)},
{Op: "replace", Path: "/identity/vendor_serial", Value: strings.TrimSpace(req.VendorSerial)},
{Op: "replace", Path: "/identity/vendor", Value: valueOrNil(normalizeOptionalString(req.Vendor))},
{Op: "replace", Path: "/identity/model", Value: valueOrNil(normalizeOptionalString(req.Model))},
{Op: "replace", Path: "/identity/machine_tag", Value: valueOrNil(normalizeOptionalString(req.AssetTag))},
}
idem := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
var idemPtr *string
if idem != "" {
idemPtr = &idem
}
_, err = h.deps.History.ApplyAssetPatch(r.Context(), historysvc.ApplyPatchCommand{
EntityID: id,
ChangeType: "ASSET_REGISTRY_UPDATED",
SourceType: "user",
ActorType: "user",
IdempotencyKey: idemPtr,
Patch: patch,
})
} else {
item, err = h.deps.Assets.Update(r.Context(), domain.Asset{
ID: id,
Name: strings.TrimSpace(req.Name),
Vendor: normalizeOptionalString(req.Vendor),
Model: normalizeOptionalString(req.Model),
VendorSerial: strings.TrimSpace(req.VendorSerial),
MachineTag: normalizeOptionalString(req.AssetTag),
})
}
if err != nil {
switch err {
case registry.ErrNotFound:
writeError(w, http.StatusNotFound, "asset not found")
case registry.ErrConflict:
writeError(w, http.StatusConflict, "asset conflict")
case historysvc.ErrNotFound:
writeError(w, http.StatusNotFound, "asset not found")
case historysvc.ErrConflict:
writeError(w, http.StatusConflict, "history conflict")
default:
writeError(w, http.StatusInternalServerError, "update asset failed")
}
return
}
if h.deps.History != nil {
item, err = h.deps.Assets.Get(r.Context(), id)
if err != nil {
writeError(w, http.StatusInternalServerError, "get asset failed")
return
}
}
writeJSON(w, http.StatusOK, item)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
@@ -242,18 +280,48 @@ func (h registryHandlers) handleComponentByID(w http.ResponseWriter, r *http.Req
return
}
item, err := h.deps.Components.Update(r.Context(), domain.Component{
ID: id,
Vendor: normalizeOptionalString(req.Vendor),
Model: normalizeOptionalString(req.Model),
VendorSerial: strings.TrimSpace(req.VendorSerial),
})
var item domain.Component
var err error
if h.deps.History != nil {
patch := []historysvc.PatchOp{
{Op: "replace", Path: "/identity/vendor_serial", Value: strings.TrimSpace(req.VendorSerial)},
{Op: "replace", Path: "/identity/vendor", Value: valueOrNil(normalizeOptionalString(req.Vendor))},
{Op: "replace", Path: "/identity/model", Value: valueOrNil(normalizeOptionalString(req.Model))},
}
idem := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
var idemPtr *string
if idem != "" {
idemPtr = &idem
}
_, err = h.deps.History.ApplyComponentPatch(r.Context(), historysvc.ApplyPatchCommand{
EntityID: id,
ChangeType: "COMPONENT_REGISTRY_UPDATED",
SourceType: "user",
ActorType: "user",
IdempotencyKey: idemPtr,
Patch: patch,
})
if err == nil {
item, err = h.deps.Components.Get(r.Context(), id)
}
} else {
item, err = h.deps.Components.Update(r.Context(), domain.Component{
ID: id,
Vendor: normalizeOptionalString(req.Vendor),
Model: normalizeOptionalString(req.Model),
VendorSerial: strings.TrimSpace(req.VendorSerial),
})
}
if err != nil {
switch err {
case registry.ErrNotFound:
writeError(w, http.StatusNotFound, "component not found")
case registry.ErrConflict:
writeError(w, http.StatusConflict, "component conflict")
case historysvc.ErrNotFound:
writeError(w, http.StatusNotFound, "component not found")
case historysvc.ErrConflict:
writeError(w, http.StatusConflict, "history conflict")
default:
writeError(w, http.StatusInternalServerError, "update component failed")
}
@@ -275,3 +343,10 @@ func normalizeOptionalString(value *string) *string {
}
return &trimmed
}
func valueOrNil(value *string) any {
if value == nil {
return nil
}
return *value
}