2023-04-24 20:52:12 +02:00
|
|
|
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 {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not list proxies", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not get proxy", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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()
|
|
|
|
|
2024-04-12 16:41:11 +02:00
|
|
|
if err := s.deleteProxyAndLayers(ctx, proxyName); err != nil {
|
2023-04-24 20:52:12 +02:00
|
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
|
|
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not delete proxy", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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 {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not parse 'name' parameter", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := url.Parse(createProxyReq.To); err != nil {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not create proxy", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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 {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(ctx, "could not update proxy", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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 {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(r.Context(), "could not parse proxy name", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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 {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(r.Context(), "could not parse int param", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-17 15:44:28 +02:00
|
|
|
func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request, param string, defaultValue []T, transform func(string) (T, error)) ([]T, bool) {
|
2023-04-24 20:52:12 +02:00
|
|
|
rawValue := r.URL.Query().Get(param)
|
|
|
|
|
|
|
|
if rawValue != "" {
|
|
|
|
rawValues := strings.Split(rawValue, ",")
|
|
|
|
values := make([]T, 0, len(rawValues))
|
|
|
|
|
|
|
|
for _, rv := range rawValues {
|
2024-05-17 15:44:28 +02:00
|
|
|
v, err := transform(rv)
|
2023-04-24 20:52:12 +02:00
|
|
|
if err != nil {
|
2023-07-05 20:05:30 +02:00
|
|
|
logAndCaptureError(r.Context(), "could not parse ids slice param", errors.WithStack(err))
|
2023-04-24 20:52:12 +02:00
|
|
|
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
|
|
|
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
values = append(values, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return values, true
|
|
|
|
}
|
|
|
|
|
|
|
|
return defaultValue, true
|
|
|
|
}
|