bouncer/internal/admin/proxy_route.go
William Petit aab5452fa2
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
feat: sentry integration
ref #3
2023-07-05 12:05:30 -06:00

312 lines
7.5 KiB
Go

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
}
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
}