feat(api): item dependency extraction, indexing, and resolve endpoints

- Add Dependency type to internal/kc and extract silo/dependencies.json
  from .kc files on commit
- Create ItemDependencyRepository with ReplaceForRevision, ListByItem,
  and Resolve (LEFT JOIN against items table)
- Add GET /{partNumber}/dependencies and
  GET /{partNumber}/dependencies/resolve endpoints
- Index dependencies in extractKCMetadata with SSE broadcast
- Pack real dependency data into .kc files on checkout
- Update PackInput.Dependencies from []any to []Dependency

Closes #143
This commit is contained in:
Forbes
2026-02-18 18:53:40 -06:00
parent c216d64702
commit cffcf56085
8 changed files with 357 additions and 7 deletions

View File

@@ -52,11 +52,33 @@ func (s *Server) packKCFile(ctx context.Context, data []byte, item *db.Item, rev
}
}
// Build dependencies from item_dependencies table.
var deps []kc.Dependency
dbDeps, err := s.deps.ListByItem(ctx, item.ID)
if err != nil {
s.logger.Warn().Err(err).Str("part_number", item.PartNumber).Msg("kc: failed to query dependencies for packing")
} else {
deps = make([]kc.Dependency, len(dbDeps))
for i, d := range dbDeps {
deps[i] = kc.Dependency{
UUID: d.ChildUUID,
PartNumber: derefStr(d.ChildPartNumber, ""),
Revision: derefInt(d.ChildRevision, 0),
Quantity: derefFloat(d.Quantity, 0),
Label: derefStr(d.Label, ""),
Relationship: d.Relationship,
}
}
}
if deps == nil {
deps = []kc.Dependency{}
}
input := &kc.PackInput{
Manifest: manifest,
Metadata: metadata,
History: history,
Dependencies: []any{}, // empty for Phase 2
Dependencies: deps,
}
return kc.Pack(data, input)
@@ -95,3 +117,19 @@ func derefStr(p *string, fallback string) string {
}
return fallback
}
// derefInt returns the value of a *int pointer, or fallback if nil.
func derefInt(p *int, fallback int) int {
if p != nil {
return *p
}
return fallback
}
// derefFloat returns the value of a *float64 pointer, or fallback if nil.
func derefFloat(p *float64, fallback float64) float64 {
if p != nil {
return *p
}
return fallback
}