package api import ( "net/http" "strings" "reanimator/internal/domain" "reanimator/internal/repository/registry" ) type RegistryDependencies struct { Customers *registry.CustomerRepository Projects *registry.ProjectRepository Assets *registry.AssetRepository Components *registry.ComponentRepository } type registryHandlers struct { deps RegistryDependencies } func RegisterRegistryRoutes(mux *http.ServeMux, deps RegistryDependencies) { h := registryHandlers{deps: deps} mux.HandleFunc("/customers", h.handleCustomers) mux.HandleFunc("/customers/", h.handleCustomerByID) mux.HandleFunc("/projects", h.handleProjects) mux.HandleFunc("/projects/", h.handleProjectByID) mux.HandleFunc("/assets", h.handleAssets) mux.HandleFunc("/components", h.handleComponents) } func (h registryHandlers) handleCustomers(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: items, err := h.deps.Customers.List(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "list customers failed") return } writeJSON(w, http.StatusOK, items) case http.MethodPost: var req struct { Name string `json:"name"` } if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if strings.TrimSpace(req.Name) == "" { writeError(w, http.StatusBadRequest, "name is required") return } item, err := h.deps.Customers.Create(r.Context(), strings.TrimSpace(req.Name)) if err != nil { switch err { case registry.ErrConflict: writeError(w, http.StatusConflict, "customer conflict") default: writeError(w, http.StatusInternalServerError, "create customer failed") } return } writeJSON(w, http.StatusCreated, item) default: w.WriteHeader(http.StatusMethodNotAllowed) } } func (h registryHandlers) handleCustomerByID(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } id, ok := parseID(r.URL.Path, "/customers/") if !ok { writeError(w, http.StatusNotFound, "customer not found") return } item, err := h.deps.Customers.Get(r.Context(), id) if err != nil { switch err { case registry.ErrNotFound: writeError(w, http.StatusNotFound, "customer not found") default: writeError(w, http.StatusInternalServerError, "get customer failed") } return } writeJSON(w, http.StatusOK, item) } func (h registryHandlers) handleProjects(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: items, err := h.deps.Projects.List(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "list projects failed") return } writeJSON(w, http.StatusOK, items) case http.MethodPost: var req struct { CustomerID int64 `json:"customer_id"` Name string `json:"name"` } if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if req.CustomerID <= 0 { writeError(w, http.StatusBadRequest, "customer_id is required") return } if strings.TrimSpace(req.Name) == "" { writeError(w, http.StatusBadRequest, "name is required") return } item, err := h.deps.Projects.Create(r.Context(), req.CustomerID, strings.TrimSpace(req.Name)) if err != nil { switch err { case registry.ErrConflict: writeError(w, http.StatusConflict, "project conflict") default: writeError(w, http.StatusInternalServerError, "create project failed") } return } writeJSON(w, http.StatusCreated, item) default: w.WriteHeader(http.StatusMethodNotAllowed) } } func (h registryHandlers) handleProjectByID(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { w.WriteHeader(http.StatusMethodNotAllowed) return } id, ok := parseID(r.URL.Path, "/projects/") if !ok { writeError(w, http.StatusNotFound, "project not found") return } item, err := h.deps.Projects.Get(r.Context(), id) if err != nil { switch err { case registry.ErrNotFound: writeError(w, http.StatusNotFound, "project not found") default: writeError(w, http.StatusInternalServerError, "get project failed") } return } writeJSON(w, http.StatusOK, item) } func (h registryHandlers) handleAssets(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: items, err := h.deps.Assets.List(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "list assets failed") return } writeJSON(w, http.StatusOK, items) case http.MethodPost: var req struct { ProjectID int64 `json:"project_id"` LocationID *int64 `json:"location_id"` Name string `json:"name"` Vendor *string `json:"vendor"` Model *string `json:"model"` VendorSerial string `json:"vendor_serial"` AssetTag *string `json:"asset_tag"` } if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if req.ProjectID <= 0 { writeError(w, http.StatusBadRequest, "project_id is required") return } if strings.TrimSpace(req.Name) == "" { writeError(w, http.StatusBadRequest, "name is required") return } if strings.TrimSpace(req.VendorSerial) == "" { writeError(w, http.StatusBadRequest, "vendor_serial is required") return } asset := domain.Asset{ ProjectID: req.ProjectID, LocationID: req.LocationID, Name: strings.TrimSpace(req.Name), Vendor: req.Vendor, Model: req.Model, VendorSerial: strings.TrimSpace(req.VendorSerial), AssetTag: req.AssetTag, } item, err := h.deps.Assets.Create(r.Context(), asset) if err != nil { switch err { case registry.ErrConflict: writeError(w, http.StatusConflict, "asset conflict") default: writeError(w, http.StatusInternalServerError, "create asset failed") } return } writeJSON(w, http.StatusCreated, item) default: w.WriteHeader(http.StatusMethodNotAllowed) } } func (h registryHandlers) handleComponents(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: items, err := h.deps.Components.List(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, "list components failed") return } writeJSON(w, http.StatusOK, items) case http.MethodPost: var req struct { LotID *int64 `json:"lot_id"` Vendor *string `json:"vendor"` Model *string `json:"model"` VendorSerial string `json:"vendor_serial"` } if err := decodeJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if strings.TrimSpace(req.VendorSerial) == "" { writeError(w, http.StatusBadRequest, "vendor_serial is required") return } component := domain.Component{ LotID: req.LotID, Vendor: req.Vendor, Model: req.Model, VendorSerial: strings.TrimSpace(req.VendorSerial), } item, err := h.deps.Components.Create(r.Context(), component) if err != nil { switch err { case registry.ErrConflict: writeError(w, http.StatusConflict, "component conflict") default: writeError(w, http.StatusInternalServerError, "create component failed") } return } writeJSON(w, http.StatusCreated, item) default: w.WriteHeader(http.StatusMethodNotAllowed) } }