func FeaturesGET(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	var result *model.Result
	var err error
	opts := &feature.Options{Sort: getSort("-_modified", r)}
	opts.Limit, opts.Offset = getLimitOffset(r)
	lID := ps.ByName("id")
Loop:
	for p, v := range r.URL.Query() {
		switch p {
		case "text":
			txt, err := url.QueryUnescape(v[0])
			if err != nil {
				StatusBadRequest(fmt.Sprintf("Query string error: %s", err)).Render(w)
				return
			}
			if l := len(txt); l < 3 || l > 32 {
				StatusBadRequest("Query string has an invalid length").Render(w)
				return
			}
			if !isAlnumBlankPunct(txt) {
				StatusBadRequest("Query string contains invalid characters").Render(w)
				return
			}
			result, err = feature.GetByText(txt, lID, opts)
			if err != nil {
				handleError(err, w)
				return
			}
			break Loop
		case "group":
			grp, err := url.QueryUnescape(v[0])
			if err != nil {
				StatusBadRequest(fmt.Sprintf("Query string error: %s", err)).Render(w)
				return
			}
			result, err = feature.GetByGroup(grp, lID, opts)
			if err != nil {
				handleError(err, w)
				return
			}
			break Loop
		}
	}
	if result == nil {
		result, err = feature.GetAll(ps.ByName("id"), opts)
		if err != nil {
			handleError(err, w)
			return
		}
	}
	view.RenderJSON(result, http.StatusOK, w)
}
FeaturesGET handles a GET request on the feature root endpoint
package controller
import (
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"github.com/tarkov-database/rest-api/model"
	"github.com/tarkov-database/rest-api/model/location"
	"github.com/tarkov-database/rest-api/model/location/feature"
	"github.com/tarkov-database/rest-api/model/location/featuregroup"
	"github.com/tarkov-database/rest-api/view"
	"github.com/google/logger"
	"github.com/julienschmidt/httprouter"
)
func LocationGET(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
	loc, err := location.GetByID(ps.ByName("id"))
	if err != nil {
		handleError(err, w)
		return
	}
	view.RenderJSON(loc, http.StatusOK, w)
}
func LocationsGET(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	var result *model.Result
	var err error
	opts := &location.Options{Sort: getSort("-_modified", r)}
	opts.Limit, opts.Offset = getLimitOffset(r)
Loop:
	for p, v := range r.URL.Query() {
		switch p {
		case "text":
			txt, err := url.QueryUnescape(v[0])
			if err != nil {
				StatusBadRequest(fmt.Sprintf("Query string error: %s", err)).Render(w)
				return
			}
			if l := len(txt); l < 3 || l > 32 {
				StatusBadRequest("Query string has an invalid length").Render(w)
				return
			}
			if !isAlnumBlankPunct(txt) {
				StatusBadRequest("Query string contains invalid characters").Render(w)
				return
			}
			result, err = location.GetByText(txt, opts)
			if err != nil {
				handleError(err, w)
				return
			}
			break Loop
		case "available":
			available, err := strconv.ParseBool(v[0])
			if err != nil {
				StatusBadRequest(err.Error()).Render(w)
				return
			}
			result, err = location.GetByAvailability(available, opts)
			if err != nil {
				handleError(err, w)
				return
			}
			break Loop
		}
	}
	if result == nil {
		result, err = location.GetAll(opts)
		if err != nil {
			handleError(err, w)
			return
		}
	}
	view.RenderJSON(result, http.StatusOK, w)
}
func LocationPOST(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	if !isSupportedMediaType(r) {
		StatusUnsupportedMediaType("Wrong content type").Render(w)
		return
	}
	loc := &location.Location{}
	if err := parseJSONBody(r.Body, loc); err != nil {
		StatusBadRequest(fmt.Sprintf("JSON parsing error: %s", err)).Render(w)
		return
	}
	if err := loc.Validate(); err != nil {
		StatusUnprocessableEntity(fmt.Sprintf("Validation error: %s", err)).Render(w)
		return
	}
	if err := location.Create(loc); err != nil {
		handleError(err, w)
		return
	}
	logger.Infof("Location %s created", loc.ID.Hex())
	view.RenderJSON(loc, http.StatusCreated, w)
}
func LocationPUT(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	if !isSupportedMediaType(r) {
		StatusUnsupportedMediaType("Wrong content type").Render(w)
		return
	}
	loc := &location.Location{}
	if err := parseJSONBody(r.Body, loc); err != nil {
		StatusBadRequest(fmt.Sprintf("JSON parsing error: %s", err)).Render(w)
		return
	}
	if err := loc.Validate(); err != nil {
		StatusUnprocessableEntity(fmt.Sprintf("Validation error: %s", err)).Render(w)
		return
	}
	id := ps.ByName("id")
	if !loc.ID.IsZero() && loc.ID.Hex() != id {
		StatusUnprocessableEntity("ID mismatch").Render(w)
		return
	}
	if err := location.Replace(id, loc); err != nil {
		handleError(err, w)
		return
	}
	logger.Infof("Location %s updated", loc.ID.Hex())
	view.RenderJSON(loc, http.StatusOK, w)
}
func LocationDELETE(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	if err := location.Remove(id); err != nil {
		handleError(err, w)
		return
	}
	logger.Infof("Location %s removed", id)
	w.WriteHeader(http.StatusNoContent)
}
func FeatureGET(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) {
	ft, err := feature.GetByID(ps.ByName("fid"), ps.ByName("id"))
	if err != nil {
		handleError(err, w)
		return
	}
	view.RenderJSON(ft, http.StatusOK, w)
}