first with full handle over rpm

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-04-25 14:49:14 +02:00
commit 274ea454dd
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
50 changed files with 4309 additions and 0 deletions

View file

@ -0,0 +1,83 @@
package api
import (
"errors"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/syonad/clonepack/internal/core"
"github.com/syonad/clonepack/internal/store"
)
type CloneHandler struct {
svc *core.CloneService
}
func NewCloneHandler(svc *core.CloneService) *CloneHandler {
return &CloneHandler{svc: svc}
}
func (h *CloneHandler) StartClone(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
}
jobID, err := h.svc.StartClone(r.Context(), repoID)
if errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "repo not found")
return
}
if errors.Is(err, core.ErrCloneAlreadyRunning) {
Error(w, http.StatusConflict, err.Error())
return
}
if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
JSON(w, http.StatusAccepted, StartCloneResponse{JobID: jobID, Status: "running"})
}
func (h *CloneHandler) GetStatus(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
}
job, err := h.svc.GetLatestCloneJob(r.Context(), repoID)
if errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "no clone job found for this repo")
return
}
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
JSON(w, http.StatusOK, cloneJobToResponse(job))
}
func cloneJobToResponse(j *store.CloneJob) CloneStatusResponse {
resp := CloneStatusResponse{
JobID: j.ID,
RepoID: j.RepoID,
Status: string(j.Status),
Error: j.Error,
CreatedAt: j.CreatedAt.Format(time.RFC3339),
}
if j.StartedAt != nil {
s := j.StartedAt.Format(time.RFC3339)
resp.StartedAt = &s
}
if j.FinishedAt != nil {
f := j.FinishedAt.Format(time.RFC3339)
resp.FinishedAt = &f
}
return resp
}

View file

@ -0,0 +1,43 @@
package api
import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/syonad/clonepack/internal/core"
"github.com/syonad/clonepack/internal/store"
)
type ProxyHandler struct {
repoSvc *core.RepoService
dataDir string
}
func NewProxyHandler(repoSvc *core.RepoService, dataDir string) *ProxyHandler {
return &ProxyHandler{repoSvc: repoSvc, dataDir: dataDir}
}
func (h *ProxyHandler) ServeFile(w http.ResponseWriter, r *http.Request) {
repoID, err := strconv.ParseInt(chi.URLParam(r, "repo_id"), 10, 64)
if err != nil {
http.Error(w, "invalid repo_id", http.StatusBadRequest)
return
}
repo, err := h.repoSvc.Get(r.Context(), repoID)
if errors.Is(err, store.ErrNotFound) {
http.NotFound(w, r)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
localDir := fmt.Sprintf("%s/repos/%d/%s", h.dataDir, repoID, repo.Type)
prefix := fmt.Sprintf("/mirror/%d", repoID)
http.StripPrefix(prefix, http.FileServer(http.Dir(localDir))).ServeHTTP(w, r)
}

View file

@ -0,0 +1,102 @@
package api
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
"github.com/syonad/clonepack/internal/core"
"github.com/syonad/clonepack/internal/store"
)
type RepoHandler struct {
svc *core.RepoService
}
func NewRepoHandler(svc *core.RepoService) *RepoHandler {
return &RepoHandler{svc: svc}
}
func (h *RepoHandler) Create(w http.ResponseWriter, r *http.Request) {
var req CreateRepoRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Error(w, http.StatusBadRequest, "invalid JSON")
return
}
repo, err := h.svc.Create(r.Context(), core.CreateRepoInput{
Name: req.Name,
Type: req.Type,
SourceURL: req.SourceURL,
SyncMode: req.SyncMode,
})
if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error())
return
}
JSON(w, http.StatusCreated, repoToResponse(repo))
}
func (h *RepoHandler) List(w http.ResponseWriter, r *http.Request) {
repos, err := h.svc.List(r.Context())
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
items := make([]RepoResponse, len(repos))
for i, repo := range repos {
items[i] = repoToResponse(&repo)
}
JSON(w, http.StatusOK, ListReposResponse{Items: items, Total: len(items)})
}
func (h *RepoHandler) Get(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid id")
return
}
repo, err := h.svc.Get(r.Context(), id)
if errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "repo not found")
return
}
if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
JSON(w, http.StatusOK, repoToResponse(repo))
}
func (h *RepoHandler) Delete(w http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid id")
return
}
if err := h.svc.Delete(r.Context(), id); 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 repoToResponse(r *store.Repo) RepoResponse {
return RepoResponse{
ID: r.ID,
Name: r.Name,
Type: r.Type,
SourceURL: r.SourceURL,
Frozen: r.Frozen,
SyncMode: r.SyncMode,
CreatedAt: r.CreatedAt,
}
}

16
internal/api/respond.go Normal file
View file

@ -0,0 +1,16 @@
package api
import (
"encoding/json"
"net/http"
)
func JSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
func Error(w http.ResponseWriter, status int, msg string) {
JSON(w, status, ErrorResponse{Error: msg})
}

50
internal/api/router.go Normal file
View file

@ -0,0 +1,50 @@
package api
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func NewRouter(repoHandler *RepoHandler, cloneHandler *CloneHandler, syncHandler *SyncHandler, snapshotHandler *SnapshotHandler, proxyHandler *ProxyHandler) http.Handler {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Get("/mirror/{repo_id}/*", proxyHandler.ServeFile)
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
JSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
r.Route("/api/v1", func(r chi.Router) {
r.Route("/repos", func(r chi.Router) {
r.Post("/", repoHandler.Create)
r.Get("/", repoHandler.List)
r.Get("/{id}", repoHandler.Get)
r.Delete("/{id}", repoHandler.Delete)
r.Post("/{id}/clone", cloneHandler.StartClone)
r.Get("/{id}/clone/status", cloneHandler.GetStatus)
r.Post("/{id}/sync/trigger", syncHandler.Trigger)
r.Get("/{id}/sync/pending", syncHandler.ListPending)
r.Post("/{id}/sync/approve", syncHandler.Approve)
r.Post("/{id}/sync/reject", syncHandler.Reject)
r.Post("/{id}/sync/block", syncHandler.Block)
r.Post("/{id}/sync/unblock", syncHandler.Unblock)
r.Get("/{id}/sync/blocked", syncHandler.ListBlocked)
r.Post("/{id}/snapshots", snapshotHandler.Create)
r.Get("/{id}/snapshots", snapshotHandler.List)
r.Get("/{id}/snapshots/diff", snapshotHandler.Diff)
r.Get("/{id}/snapshots/{snap_id}", snapshotHandler.Get)
r.Delete("/{id}/snapshots/{snap_id}", snapshotHandler.Delete)
r.Post("/{id}/snapshots/{snap_id}/rollback", snapshotHandler.Rollback)
})
})
return r
}

View file

@ -0,0 +1,196 @@
package api
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/syonad/clonepack/internal/core"
"github.com/syonad/clonepack/internal/store"
)
type SnapshotHandler struct {
svc *core.SnapshotService
}
func NewSnapshotHandler(svc *core.SnapshotService) *SnapshotHandler {
return &SnapshotHandler{svc: svc}
}
func (h *SnapshotHandler) Create(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 CreateSnapshotRequest
json.NewDecoder(r.Body).Decode(&req)
if req.Label == "" {
req.Label = "manual-" + time.Now().UTC().Format(time.RFC3339)
}
id, err := h.svc.TakeSnapshot(r.Context(), repoID, req.Label)
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
}
snap, _, _ := h.svc.Get(r.Context(), repoID, id)
JSON(w, http.StatusCreated, snapshotToResponse(*snap))
}
func (h *SnapshotHandler) List(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
}
snaps, err := h.svc.List(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([]SnapshotResponse, len(snaps))
for i, s := range snaps {
items[i] = snapshotToResponse(s)
}
JSON(w, http.StatusOK, ListSnapshotsResponse{Items: items, Total: len(items)})
}
func (h *SnapshotHandler) Get(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
}
snapID, err := strconv.ParseInt(chi.URLParam(r, "snap_id"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid snap_id")
return
}
snap, pkgs, err := h.svc.Get(r.Context(), repoID, snapID)
if errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "snapshot not found")
return
} else if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
resp := SnapshotDetailResponse{
SnapshotResponse: snapshotToResponse(*snap),
Packages: make([]SnapshotPackageResponse, len(pkgs)),
}
for i, p := range pkgs {
resp.Packages[i] = snapshotPkgToResponse(p)
}
JSON(w, http.StatusOK, resp)
}
func (h *SnapshotHandler) Delete(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
}
snapID, err := strconv.ParseInt(chi.URLParam(r, "snap_id"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid snap_id")
return
}
if err := h.svc.Delete(r.Context(), repoID, snapID); errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "snapshot not found")
return
} else if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *SnapshotHandler) Diff(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
}
fromID, err := strconv.ParseInt(r.URL.Query().Get("from"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid from param")
return
}
toID, err := strconv.ParseInt(r.URL.Query().Get("to"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid to param")
return
}
diff, err := h.svc.Diff(r.Context(), repoID, fromID, toID)
if errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "snapshot not found")
return
} else if err != nil {
Error(w, http.StatusBadRequest, err.Error())
return
}
resp := SnapshotDiffResponse{
From: snapshotToResponse(*diff.From),
To: snapshotToResponse(*diff.To),
Unchanged: diff.Unchanged,
}
for _, p := range diff.Added {
resp.Added = append(resp.Added, snapshotPkgToResponse(p))
}
for _, p := range diff.Removed {
resp.Removed = append(resp.Removed, snapshotPkgToResponse(p))
}
JSON(w, http.StatusOK, resp)
}
func (h *SnapshotHandler) Rollback(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
}
snapID, err := strconv.ParseInt(chi.URLParam(r, "snap_id"), 10, 64)
if err != nil {
Error(w, http.StatusBadRequest, "invalid snap_id")
return
}
if err := h.svc.Rollback(r.Context(), repoID, snapID); errors.Is(err, store.ErrNotFound) {
Error(w, http.StatusNotFound, "snapshot not found")
return
} else if err != nil {
Error(w, http.StatusInternalServerError, err.Error())
return
}
JSON(w, http.StatusOK, map[string]string{"status": "rollback completed"})
}
func snapshotToResponse(s store.Snapshot) SnapshotResponse {
return SnapshotResponse{
ID: s.ID,
RepoID: s.RepoID,
Label: s.Label,
CreatedAt: s.CreatedAt,
}
}
func snapshotPkgToResponse(p store.SnapshotPackage) SnapshotPackageResponse {
return SnapshotPackageResponse{
ID: p.ID,
Name: p.Name,
Version: p.Version,
Arch: p.Arch,
Location: p.Location,
Checksum: p.Checksum,
ChecksumType: p.ChecksumType,
Size: p.Size,
}
}

View file

@ -0,0 +1,43 @@
package api
import "time"
type SnapshotResponse struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Label string `json:"label"`
CreatedAt time.Time `json:"created_at"`
}
type SnapshotDetailResponse struct {
SnapshotResponse
Packages []SnapshotPackageResponse `json:"packages"`
}
type SnapshotPackageResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Arch string `json:"arch"`
Location string `json:"location"`
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
}
type ListSnapshotsResponse struct {
Items []SnapshotResponse `json:"items"`
Total int `json:"total"`
}
type CreateSnapshotRequest struct {
Label string `json:"label"`
}
type SnapshotDiffResponse struct {
From SnapshotResponse `json:"from"`
To SnapshotResponse `json:"to"`
Added []SnapshotPackageResponse `json:"added"`
Removed []SnapshotPackageResponse `json:"removed"`
Unchanged int `json:"unchanged"`
}

View file

@ -0,0 +1,191 @@
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,
}
}

View file

@ -0,0 +1,38 @@
package api
import "time"
type SyncSelectionRequest struct {
IDs []int64 `json:"ids"`
}
type PendingPackageResponse struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
Version string `json:"version"`
Arch string `json:"arch"`
Location string `json:"location"`
Checksum string `json:"checksum"`
ChecksumType string `json:"checksum_type"`
Size int64 `json:"size"`
CreatedAt time.Time `json:"created_at"`
}
type ListPendingResponse struct {
Items []PendingPackageResponse `json:"items"`
Total int `json:"total"`
}
type BlockedPackageResponse struct {
ID int64 `json:"id"`
RepoID int64 `json:"repo_id"`
Name string `json:"name"`
Location string `json:"location"`
CreatedAt time.Time `json:"created_at"`
}
type ListBlockedResponse struct {
Items []BlockedPackageResponse `json:"items"`
Total int `json:"total"`
}

44
internal/api/types.go Normal file
View file

@ -0,0 +1,44 @@
package api
import "time"
type CreateRepoRequest struct {
Name string `json:"name"`
Type string `json:"type"`
SourceURL string `json:"source_url"`
SyncMode string `json:"sync_mode,omitempty"`
}
type RepoResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
SourceURL string `json:"source_url"`
Frozen bool `json:"frozen"`
SyncMode string `json:"sync_mode"`
CreatedAt time.Time `json:"created_at"`
}
type ListReposResponse struct {
Items []RepoResponse `json:"items"`
Total int `json:"total"`
}
type ErrorResponse struct {
Error string `json:"error"`
}
type StartCloneResponse struct {
JobID int64 `json:"job_id"`
Status string `json:"status"`
}
type CloneStatusResponse struct {
JobID int64 `json:"job_id"`
RepoID int64 `json:"repo_id"`
Status string `json:"status"`
StartedAt *string `json:"started_at,omitempty"`
FinishedAt *string `json:"finished_at,omitempty"`
Error *string `json:"error,omitempty"`
CreatedAt string `json:"created_at"`
}