1package admin
2
3import (
4 "encoding/json"
5 "net/http"
6
7 "github.com/bnema/zerowrap"
8
9 "github.com/bnema/gordon/internal/adapters/dto"
10 "github.com/bnema/gordon/internal/domain"
11)
12
13// handleListVolumes handles GET /admin/volumes endpoint.
14func (h *Handler) handleListVolumes(w http.ResponseWriter, r *http.Request) {
15 if r.Method != http.MethodGet {
16 h.sendError(w, http.StatusMethodNotAllowed, "method not allowed")
17 return
18 }
19
20 ctx := r.Context()
21
22 if !HasAccess(ctx, domain.AdminResourceVolumes, domain.AdminActionRead) {
23 h.sendError(w, http.StatusForbidden, "insufficient permissions for volumes:read")
24 return
25 }
26
27 if h.volumeSvc == nil {
28 h.sendError(w, http.StatusServiceUnavailable, "volume service not available")
29 return
30 }
31
32 vols, err := h.volumeSvc.ListVolumes(ctx)
33 if err != nil {
34 log := zerowrap.FromCtx(ctx)
35 log.Error().Err(err).Msg("failed to list volumes")
36 h.sendError(w, http.StatusInternalServerError, "failed to list volumes")
37 return
38 }
39
40 result := make([]dto.Volume, len(vols))
41 for i, v := range vols {
42 result[i] = dto.Volume{
43 Name: v.Name,
44 Driver: v.Driver,
45 MountPoint: v.MountPoint,
46 Size: v.Size,
47 CreatedAt: v.CreatedAt,
48 InUse: v.InUse,
49 Containers: v.Containers,
50 Labels: v.Labels,
51 }
52 }
53
54 h.sendJSON(w, http.StatusOK, result)
55}
56
57// handlePruneVolumes handles POST /admin/volumes/prune endpoint.
58func (h *Handler) handlePruneVolumes(w http.ResponseWriter, r *http.Request) {
59 if r.Method != http.MethodPost {
60 h.sendError(w, http.StatusMethodNotAllowed, "method not allowed")
61 return
62 }
63
64 ctx := r.Context()
65
66 if !HasAccess(ctx, domain.AdminResourceVolumes, domain.AdminActionWrite) {
67 h.sendError(w, http.StatusForbidden, "insufficient permissions for volumes:write")
68 return
69 }
70
71 if h.volumeSvc == nil {
72 h.sendError(w, http.StatusServiceUnavailable, "volume service not available")
73 return
74 }
75
76 var req dto.VolumePruneRequest
77 r.Body = http.MaxBytesReader(w, r.Body, maxAdminRequestSize)
78 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
79 h.sendError(w, http.StatusBadRequest, "invalid request body")
80 return
81 }
82
83 report, removed, err := h.volumeSvc.PruneVolumes(ctx, req.DryRun)
84 if err != nil {
85 log := zerowrap.FromCtx(ctx)
86 log.Error().Err(err).Msg("failed to prune volumes")
87 h.sendError(w, http.StatusInternalServerError, "failed to prune volumes")
88 return
89 }
90
91 vols := make([]dto.Volume, len(removed))
92 for i, v := range removed {
93 vols[i] = dto.Volume{
94 Name: v.Name,
95 Size: v.Size,
96 }
97 }
98
99 h.sendJSON(w, http.StatusOK, dto.VolumePruneResponse{
100 VolumesRemoved: report.VolumesRemoved,
101 SpaceReclaimed: report.SpaceReclaimed,
102 Volumes: vols,
103 })
104}