package web import ( "io" "net/http" "strings" "encoding/json" "github.com/go-chi/chi/v5" "github.com/darroyo/nasctl/internal/db" "github.com/darroyo/nasctl/internal/modules/nfs" "github.com/darroyo/nasctl/internal/system" ) type nfsExportRequest struct { Path string `json:"path"` Clients []string `json:"clients"` Options string `json:"options"` } func (req nfsExportRequest) validate(allowedRoots []string) error { if err := system.ValidatePathAllowed(req.Path, allowedRoots); err != nil { return err } for _, client := range req.Clients { if err := system.ValidateNFSClient(client); err != nil { return err } } if strings.TrimSpace(req.Options) == "" { return nil } return system.ValidateNFSOptions(req.Options) } func (req nfsExportRequest) toModel() db.NFSExport { options := strings.TrimSpace(req.Options) if options == "" { options = "rw,sync,no_root_squash" } return db.NFSExport{ Path: req.Path, Clients: req.Clients, Options: options, } } func (s *Server) handleListNFSExports(w http.ResponseWriter, r *http.Request) { exports, err := s.DB.ListNFSExports() if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]any{"exports": exports}) } func (s *Server) handleGetNFSExport(w http.ResponseWriter, r *http.Request) { id, err := parseID(chi.URLParam(r, "id")) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } export, err := s.DB.GetNFSExport(id) if err != nil { writeError(w, http.StatusNotFound, err.Error()) return } writeJSON(w, http.StatusOK, export) } func (s *Server) handleCreateNFSExport(w http.ResponseWriter, r *http.Request) { req, err := decodeNFSExportRequest(r.Body) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if err := req.validate(s.AllowedRoots); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } export, err := s.DB.CreateNFSExport(req.toModel()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if err := s.DB.MarkDirty(nfs.ModuleName); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusCreated, export) } func (s *Server) handleUpdateNFSExport(w http.ResponseWriter, r *http.Request) { id, err := parseID(chi.URLParam(r, "id")) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } req, err := decodeNFSExportRequest(r.Body) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } if err := req.validate(s.AllowedRoots); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } export, err := s.DB.UpdateNFSExport(id, req.toModel()) if err != nil { writeError(w, http.StatusNotFound, err.Error()) return } if err := s.DB.MarkDirty(nfs.ModuleName); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, export) } func (s *Server) handleDeleteNFSExport(w http.ResponseWriter, r *http.Request) { id, err := parseID(chi.URLParam(r, "id")) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } if err := s.DB.DeleteNFSExport(id); err != nil { writeError(w, http.StatusNotFound, err.Error()) return } if err := s.DB.MarkDirty(nfs.ModuleName); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } w.WriteHeader(http.StatusNoContent) } func decodeNFSExportRequest(body io.ReadCloser) (nfsExportRequest, error) { defer body.Close() var req nfsExportRequest if err := json.NewDecoder(body).Decode(&req); err != nil { return nfsExportRequest{}, err } return req, nil }