package admin import ( "net/http" "net/url" "sort" "strconv" "strings" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/go-chi/chi/v5" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/api" ) type QueryProxyResponse struct { Proxies []*store.ProxyHeader `json:"proxies"` } func (s *Server) queryProxy(w http.ResponseWriter, r *http.Request) { options := []store.QueryProxyOptionFunc{} names, ok := getStringableSliceValues(w, r, "names", nil, validateProxyName) if !ok { return } if names != nil { options = append(options, store.WithProxyQueryNames(names...)) } ctx := r.Context() proxies, err := s.proxyRepository.QueryProxy( ctx, options..., ) if err != nil { logAndCaptureError(ctx, "could not list proxies", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } sort.Sort(store.ByProxyWeight(proxies)) api.DataResponse(w, http.StatusOK, QueryProxyResponse{ Proxies: proxies, }) } func validateProxyName(v string) (store.ProxyName, error) { name, err := store.ValidateName(v) if err != nil { return "", errors.WithStack(err) } return store.ProxyName(name), nil } type GetProxyResponse struct { Proxy *store.Proxy `json:"proxy"` } func (s *Server) getProxy(w http.ResponseWriter, r *http.Request) { proxyName, ok := getProxyName(w, r) if !ok { return } ctx := r.Context() proxy, err := s.proxyRepository.GetProxy(ctx, proxyName) if err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not get proxy", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, GetProxyResponse{ Proxy: proxy, }) } type DeleteProxyResponse struct { ProxyName store.ProxyName `json:"proxyName"` } func (s *Server) deleteProxy(w http.ResponseWriter, r *http.Request) { proxyName, ok := getProxyName(w, r) if !ok { return } ctx := r.Context() if err := s.proxyRepository.DeleteProxy(ctx, proxyName); err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not delete proxy", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } layers, err := s.layerRepository.QueryLayers(ctx, proxyName) if err != nil { logAndCaptureError(ctx, "could not query proxy's layers", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } for _, layer := range layers { if err := s.layerRepository.DeleteLayer(ctx, proxyName, layer.Name); err != nil { logAndCaptureError(ctx, "could not delete layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } } api.DataResponse(w, http.StatusOK, DeleteProxyResponse{ ProxyName: proxyName, }) } type CreateProxyRequest struct { Name string `json:"name" validate:"required"` To string `json:"to" validate:"required"` From []string `json:"from" validate:"required"` } type CreateProxyResponse struct { Proxy *store.Proxy `json:"proxy"` } func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() createProxyReq := &CreateProxyRequest{} if ok := api.Bind(w, r, createProxyReq); !ok { return } name, err := store.ValidateName(createProxyReq.Name) if err != nil { logAndCaptureError(ctx, "could not parse 'name' parameter", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return } if _, err := url.Parse(createProxyReq.To); err != nil { logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return } proxy, err := s.proxyRepository.CreateProxy(ctx, store.ProxyName(name), createProxyReq.To, createProxyReq.From...) if err != nil { if errors.Is(err, store.ErrAlreadyExist) { api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil) return } logAndCaptureError(ctx, "could not create proxy", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, struct { Proxy *store.Proxy `json:"proxy"` }{ Proxy: proxy, }) } type UpdateProxyRequest struct { Enabled *bool `json:"enabled"` Weight *int `json:"weight"` To *string `json:"to"` From []string `json:"from"` } type UpdateProxyResponse struct { Proxy *store.Proxy `json:"proxy"` } func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) { ctx := r.Context() proxyName, ok := getProxyName(w, r) if !ok { return } updateProxyReq := &UpdateProxyRequest{} if ok := api.Bind(w, r, updateProxyReq); !ok { return } options := make([]store.UpdateProxyOptionFunc, 0) if updateProxyReq.Enabled != nil { options = append(options, store.WithProxyUpdateEnabled(*updateProxyReq.Enabled)) } if updateProxyReq.To != nil { _, err := url.Parse(*updateProxyReq.To) if err != nil { logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return } options = append(options, store.WithProxyUpdateTo(*updateProxyReq.To)) } if updateProxyReq.From != nil { options = append(options, store.WithProxyUpdateFrom(updateProxyReq.From...)) } if updateProxyReq.Weight != nil { options = append(options, store.WithProxyUpdateWeight(*updateProxyReq.Weight)) } proxy, err := s.proxyRepository.UpdateProxy( ctx, proxyName, options..., ) if err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not update proxy", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, UpdateProxyResponse{Proxy: proxy}) } func getProxyName(w http.ResponseWriter, r *http.Request) (store.ProxyName, bool) { rawProxyName := chi.URLParam(r, "proxyName") name, err := store.ValidateName(rawProxyName) if err != nil { logAndCaptureError(r.Context(), "could not parse proxy name", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return "", false } return store.ProxyName(name), true } func getIntQueryParam(w http.ResponseWriter, r *http.Request, param string, defaultValue int64) (int64, bool) { rawValue := r.URL.Query().Get(param) if rawValue != "" { value, err := strconv.ParseInt(rawValue, 10, 64) if err != nil { logAndCaptureError(r.Context(), "could not parse int param", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return 0, false } return value, true } return defaultValue, true } func getStringSliceValues(w http.ResponseWriter, r *http.Request, param string, defaultValue []string) ([]string, bool) { rawValue := r.URL.Query().Get(param) if rawValue != "" { values := strings.Split(rawValue, ",") return values, true } return defaultValue, true } func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request, param string, defaultValue []T, validate func(string) (T, error)) ([]T, bool) { rawValue := r.URL.Query().Get(param) if rawValue != "" { rawValues := strings.Split(rawValue, ",") values := make([]T, 0, len(rawValues)) for _, rv := range rawValues { v, err := validate(rv) if err != nil { logAndCaptureError(r.Context(), "could not parse ids slice param", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return nil, false } values = append(values, v) } return values, true } return defaultValue, true }