package api import ( "context" "encoding/json" "errors" "log" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/syonad/clonepack/internal/core" "github.com/syonad/clonepack/internal/store" ) type SyncHandler struct { svc *core.SyncService } func NewSyncHandler(svc *core.SyncService) *SyncHandler { return &SyncHandler{svc: svc} } func (h *SyncHandler) Trigger(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } // Validate repo exists before returning. if err := h.svc.ValidateRepo(r.Context(), repoID); errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } go func() { if err := h.svc.ScanRepo(context.Background(), repoID); err != nil { log.Printf("scan repo %d: %v", repoID, err) } }() JSON(w, http.StatusAccepted, map[string]string{"status": "scan started"}) } func (h *SyncHandler) ListPending(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } pkgs, err := h.svc.ListPending(r.Context(), repoID) if errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } items := make([]PendingPackageResponse, len(pkgs)) for i, p := range pkgs { items[i] = pendingToResponse(p) } JSON(w, http.StatusOK, ListPendingResponse{Items: items, Total: len(items)}) } func (h *SyncHandler) Approve(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var req SyncSelectionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || len(req.IDs) == 0 { Error(w, http.StatusBadRequest, "body must contain non-empty ids array") return } if err := h.svc.ValidateRepo(r.Context(), repoID); errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } ids := req.IDs go func() { if err := h.svc.ApprovePending(context.Background(), repoID, ids); err != nil { log.Printf("approve pending for repo %d: %v", repoID, err) } }() JSON(w, http.StatusAccepted, map[string]string{"status": "approval started"}) } func (h *SyncHandler) Reject(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var req SyncSelectionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || len(req.IDs) == 0 { Error(w, http.StatusBadRequest, "body must contain non-empty ids array") return } if err := h.svc.RejectPending(r.Context(), repoID, req.IDs); errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) } func (h *SyncHandler) Block(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var req SyncSelectionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || len(req.IDs) == 0 { Error(w, http.StatusBadRequest, "body must contain non-empty ids array") return } if err := h.svc.BlockPackages(r.Context(), repoID, req.IDs); errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) } func (h *SyncHandler) Unblock(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var req SyncSelectionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil || len(req.IDs) == 0 { Error(w, http.StatusBadRequest, "body must contain non-empty ids array") return } if err := h.svc.UnblockPackages(r.Context(), repoID, req.IDs); errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) } func (h *SyncHandler) ListBlocked(w http.ResponseWriter, r *http.Request) { repoID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } pkgs, err := h.svc.ListBlocked(r.Context(), repoID) if errors.Is(err, store.ErrNotFound) { Error(w, http.StatusNotFound, "repo not found") return } else if err != nil { Error(w, http.StatusInternalServerError, err.Error()) return } items := make([]BlockedPackageResponse, len(pkgs)) for i, p := range pkgs { items[i] = BlockedPackageResponse{ID: p.ID, RepoID: p.RepoID, Name: p.Name, Location: p.Location, CreatedAt: p.CreatedAt} } JSON(w, http.StatusOK, ListBlockedResponse{Items: items, Total: len(items)}) } func pendingToResponse(p store.PendingPackage) PendingPackageResponse { return PendingPackageResponse{ ID: p.ID, RepoID: p.RepoID, Name: p.Name, Version: p.Version, Arch: p.Arch, Location: p.Location, Checksum: p.Checksum, ChecksumType: p.ChecksumType, Size: p.Size, CreatedAt: p.CreatedAt, } }