package admin import ( "fmt" "net/http" "sort" "forge.cadoles.com/cadoles/bouncer/internal/schema" "forge.cadoles.com/cadoles/bouncer/internal/setup" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/go-chi/chi/v5" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/api" ) type QueryLayerResponse struct { Layers []*store.LayerHeader `json:"layers"` } func (s *Server) queryLayer(w http.ResponseWriter, r *http.Request) { proxyName, ok := getProxyName(w, r) if !ok { return } options := []store.QueryLayerOptionFunc{} name := r.URL.Query().Get("name") if name != "" { options = append(options, store.WithLayerQueryName(store.LayerName(name))) } ctx := r.Context() layers, err := s.layerRepository.QueryLayers( ctx, proxyName, options..., ) if err != nil { logAndCaptureError(ctx, "could not list layers", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } sort.Sort(store.ByLayerWeight(layers)) api.DataResponse(w, http.StatusOK, QueryLayerResponse{ Layers: layers, }) } func validateLayerName(v string) (store.LayerName, error) { name, err := store.ValidateName(v) if err != nil { return "", errors.WithStack(err) } return store.LayerName(name), nil } type GetLayerResponse struct { Layer *store.Layer `json:"layer"` } func (s *Server) getLayer(w http.ResponseWriter, r *http.Request) { proxyName, ok := getProxyName(w, r) if !ok { return } layerName, ok := getLayerName(w, r) if !ok { return } ctx := r.Context() layer, err := s.layerRepository.GetLayer(ctx, proxyName, layerName) if err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not get layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, GetLayerResponse{ Layer: layer, }) } type DeleteLayerResponse struct { LayerName store.LayerName `json:"layerName"` } func (s *Server) deleteLayer(w http.ResponseWriter, r *http.Request) { proxyName, ok := getProxyName(w, r) if !ok { return } layerName, ok := getLayerName(w, r) if !ok { return } ctx := r.Context() if err := s.layerRepository.DeleteLayer(ctx, proxyName, layerName); err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not delete layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, DeleteLayerResponse{ LayerName: layerName, }) } type CreateLayerRequest struct { Name string `json:"name" validate:"required"` Type string `json:"type" validate:"required"` Options map[string]any `json:"options"` } type CreateLayerResponse struct { Layer *store.Layer `json:"layer"` } func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) { ctx := r.Context() proxyName, ok := getProxyName(w, r) if !ok { return } createLayerReq := &CreateLayerRequest{} if ok := api.Bind(w, r, createLayerReq); !ok { return } layerName, err := store.ValidateName(createLayerReq.Name) if err != nil { logAndCaptureError(ctx, "invalid 'name' parameter", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil) return } layerType := store.LayerType(createLayerReq.Type) if !setup.LayerTypeExists(layerType) { logAndCaptureError(ctx, fmt.Sprintf("unknown layer type '%s'", layerType), errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil) return } layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), layerType, createLayerReq.Options) if err != nil { if errors.Is(err, store.ErrAlreadyExist) { api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil) return } logAndCaptureError(ctx, "could not create layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, struct { Layer *store.Layer `json:"layer"` }{ Layer: layer, }) } type UpdateLayerRequest struct { Enabled *bool `json:"enabled"` Weight *int `json:"weight"` Options *store.LayerOptions `json:"options"` } type UpdateLayerResponse struct { Layer *store.Layer `json:"layer"` } func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) { ctx := r.Context() proxyName, ok := getProxyName(w, r) if !ok { return } layerName, ok := getLayerName(w, r) if !ok { return } layer, err := s.layerRepository.GetLayer(ctx, proxyName, layerName) if err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not get layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } updateLayerReq := &UpdateLayerRequest{} if ok := api.Bind(w, r, updateLayerReq); !ok { return } options := make([]store.UpdateLayerOptionFunc, 0) if updateLayerReq.Enabled != nil { options = append(options, store.WithLayerUpdateEnabled(*updateLayerReq.Enabled)) } if updateLayerReq.Weight != nil { options = append(options, store.WithLayerUpdateWeight(*updateLayerReq.Weight)) } if updateLayerReq.Options != nil { layerOptionsSchema, err := setup.GetLayerOptionsSchema(layer.Type) if err != nil { logAndCaptureError(ctx, "could not retrieve layer options schema", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } rawOptions := func(opts *store.LayerOptions) map[string]any { return *opts }(updateLayerReq.Options) if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil { logAndCaptureError(ctx, "could not validate layer options", errors.WithStack(err)) var invalidDataErr *schema.InvalidDataError if errors.As(err, &invalidDataErr) { invalidDataErrorResponse(w, r, invalidDataErr) return } api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } options = append(options, store.WithLayerUpdateOptions(*updateLayerReq.Options)) } layer, err = s.layerRepository.UpdateLayer( ctx, proxyName, layerName, options..., ) if err != nil { if errors.Is(err, store.ErrNotFound) { api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil) return } logAndCaptureError(ctx, "could not update layer", errors.WithStack(err)) api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil) return } api.DataResponse(w, http.StatusOK, UpdateLayerResponse{Layer: layer}) } func getLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool) { rawLayerName := chi.URLParam(r, "layerName") name, err := store.ValidateName(rawLayerName) if err != nil { logAndCaptureError(r.Context(), "could not parse layer name", errors.WithStack(err)) api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) return "", false } return store.LayerName(name), true }