package api import ( "net/http" "strings" "reanimator/internal/history" "reanimator/internal/repository/registry" "reanimator/internal/repository/timeline" ) type AssetComponentDependencies struct { Assets *registry.AssetRepository Components *registry.ComponentRepository Installations *registry.InstallationRepository Timeline *timeline.EventRepository History *history.Service } type assetComponentHandlers struct { deps AssetComponentDependencies } func RegisterAssetComponentRoutes(mux *http.ServeMux, deps AssetComponentDependencies) { h := assetComponentHandlers{deps: deps} mux.HandleFunc("/api/assets/", h.handleAssetAPI) mux.HandleFunc("/api/components/search-lite", h.handleComponentSearchLite) mux.HandleFunc("/assets/", h.handleAsset) mux.HandleFunc("/components/", h.handleComponent) } func (h assetComponentHandlers) handleAsset(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/timeline") { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } if h.deps.Timeline == nil { if h.deps.History == nil { writeError(w, http.StatusInternalServerError, "timeline unavailable") return } } if h.deps.History != nil { id := parseTimelineID(r.URL.Path, "/assets/") if id == "" { writeError(w, http.StatusNotFound, "asset timeline not found") return } historyHandlers{deps: HistoryDependencies{Service: h.deps.History}}.handleTimeline(w, r, "asset", id) return } id := parseTimelineID(r.URL.Path, "/assets/") if id == "" { writeError(w, http.StatusNotFound, "asset timeline not found") return } writeTimelineResponse(w, r, h.deps.Timeline, "asset", id) return } if strings.HasSuffix(r.URL.Path, "/components") { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } if h.deps.Installations == nil { writeError(w, http.StatusInternalServerError, "installations unavailable") return } id, ok := parseSubresourceID(r.URL.Path, "/assets/", "/components") if !ok { writeError(w, http.StatusNotFound, "asset components not found") return } items, err := h.deps.Installations.ListCurrentComponentsByAsset(r.Context(), id) if err != nil { writeError(w, http.StatusInternalServerError, "list components failed") return } writeJSON(w, http.StatusOK, map[string]any{"items": items}) return } if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } id, ok := parseID(r.URL.Path, "/assets/") if !ok { writeError(w, http.StatusNotFound, "asset not found") return } item, err := h.deps.Assets.Get(r.Context(), id) if err != nil { switch err { case registry.ErrNotFound: writeError(w, http.StatusNotFound, "asset not found") default: writeError(w, http.StatusInternalServerError, "get asset failed") } return } writeJSON(w, http.StatusOK, item) } func (h assetComponentHandlers) handleComponent(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "/timeline") { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } if h.deps.Timeline == nil { if h.deps.History == nil { writeError(w, http.StatusInternalServerError, "timeline unavailable") return } } if h.deps.History != nil { id := parseTimelineID(r.URL.Path, "/components/") if id == "" { writeError(w, http.StatusNotFound, "component timeline not found") return } historyHandlers{deps: HistoryDependencies{Service: h.deps.History}}.handleTimeline(w, r, "component", id) return } id := parseTimelineID(r.URL.Path, "/components/") if id == "" { writeError(w, http.StatusNotFound, "component timeline not found") return } writeTimelineResponse(w, r, h.deps.Timeline, "component", id) return } if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } id, ok := parseID(r.URL.Path, "/components/") if !ok { writeError(w, http.StatusNotFound, "component not found") return } item, err := h.deps.Components.Get(r.Context(), id) if err != nil { switch err { case registry.ErrNotFound: writeError(w, http.StatusNotFound, "component not found") default: writeError(w, http.StatusInternalServerError, "get component failed") } return } writeJSON(w, http.StatusOK, item) }