From 742598da0e3a2ee03aa0a73760c4c745ed248aa3 Mon Sep 17 00:00:00 2001 From: Markus Wiegand Date: Fri, 12 Jan 2024 22:30:00 +0100 Subject: [PATCH] update filter options --- controller/helper.go | 6 ++ controller/item.go | 148 +++++++++++++++++++++++---------- model/item/item.go | 8 +- model/item/kind_ammunition.go | 27 ++++++ model/item/kind_armor.go | 38 +++++++++ model/item/kind_firearm.go | 37 +++++++++ model/item/kind_magazine.go | 22 +++++ model/item/kind_tacticalRig.go | 41 +++++++++ model/model.go | 66 +++------------ 9 files changed, 291 insertions(+), 102 deletions(-) diff --git a/controller/helper.go b/controller/helper.go index bf66519..d39406d 100644 --- a/controller/helper.go +++ b/controller/helper.go @@ -54,6 +54,12 @@ func isAlnumBlankPunct(s string) bool { return !regexNonAlnumBlankPunct.MatchString(s) } +var regexNotAllowedQueryChars = regexp.MustCompile(`[^[:alnum:][:blank:]!#%&'()*+,\-./:;?_~]`) + +func isAllowedQueryChars(s string) bool { + return !regexNotAllowedQueryChars.MatchString(s) +} + func handleError(err error, w http.ResponseWriter) { var res *Status diff --git a/controller/item.go b/controller/item.go index be62770..92f32a9 100644 --- a/controller/item.go +++ b/controller/item.go @@ -134,87 +134,149 @@ Loop: } if result == nil { - filter := model.Filter{} + var filter model.DocumentFilter switch kind { case item.KindArmor: - err = filter.AddString("type", r.URL.Query().Get("type")) - if err != nil { - break + armorFilter := &item.ArmorFilter{} + + if v := r.URL.Query().Get("type"); v != "" { + if !isAllowedQueryChars(v) { + break + } + armorFilter.Type = &v } - err = filter.AddInt("armor.class", r.URL.Query().Get("armor.class")) - if err != nil { - break + if v := r.URL.Query().Get("armor.class"); v != "" { + if v, err := strconv.ParseInt(v, 10, 64); err == nil { + armorFilter.ArmorClass = &v + } else { + break + } } - err = filter.AddString("armor.material.name", r.URL.Query().Get("armor.material.name")) - if err != nil { - break + if v := r.URL.Query().Get("armor.material.name"); v != "" { + if !isAllowedQueryChars(v) { + break + } + armorFilter.MaterialName = &v } + + filter = armorFilter case item.KindFirearm: - err = filter.AddString("manufacturer", r.URL.Query().Get("manufacturer")) - if err != nil { - break + firearmFilter := &item.FirearmFilter{} + + if v := r.URL.Query().Get("type"); v != "" { + if !isAllowedQueryChars(v) { + break + } + firearmFilter.Type = &v } - err = filter.AddString("type", r.URL.Query().Get("type")) - if err != nil { - break + if v := r.URL.Query().Get("class"); v != "" { + if !isAllowedQueryChars(v) { + break + } + firearmFilter.Class = &v } - err = filter.AddString("class", r.URL.Query().Get("class")) - if err != nil { - break + if v := r.URL.Query().Get("caliber"); v != "" { + if !isAllowedQueryChars(v) { + break + } + firearmFilter.Caliber = &v } - err = filter.AddString("caliber", r.URL.Query().Get("caliber")) - if err != nil { - break + if v := r.URL.Query().Get("manufacturer"); v != "" { + if !isAllowedQueryChars(v) { + break + } + firearmFilter.Manufacturer = &v } + + filter = firearmFilter case item.KindTacticalrig: - if v := r.URL.Query().Get("armored"); v != "" { + tacticalrigFilter := &item.TacticalRigFilter{} + + if v := r.URL.Query().Get("isPlateCarrier"); v != "" { if v, err := strconv.ParseBool(v); err == nil { - filter["armor"] = bson.D{{Key: "$exists", Value: v}} + tacticalrigFilter.IsPlateCarrier = &v + } else { + break } } - err = filter.AddInt("armor.class", r.URL.Query().Get("armor.class")) - if err != nil { - break + if v := r.URL.Query().Get("isArmored"); v != "" { + if v, err := strconv.ParseBool(v); err == nil { + tacticalrigFilter.IsArmored = &v + } else { + break + } } - err = filter.AddString("armor.material.name", r.URL.Query().Get("armor.material.name")) - if err != nil { - break + if v := r.URL.Query().Get("armor.class"); v != "" { + if v, err := strconv.ParseInt(v, 10, 64); err == nil { + tacticalrigFilter.ArmorClass = &v + } else { + break + } + } + + if v := r.URL.Query().Get("armor.material"); v != "" { + if !isAllowedQueryChars(v) { + break + } + tacticalrigFilter.ArmorMaterial = &v } + + filter = tacticalrigFilter case item.KindAmmunition: - err = filter.AddString("type", r.URL.Query().Get("type")) - if err != nil { - break + ammunitionFilter := &item.AmmunitionFilter{} + + if v := r.URL.Query().Get("type"); v != "" { + if !isAllowedQueryChars(v) { + break + } + ammunitionFilter.Type = &v } - err = filter.AddString("caliber", r.URL.Query().Get("caliber")) - if err != nil { - break + if v := r.URL.Query().Get("caliber"); v != "" { + if !isAllowedQueryChars(v) { + break + } + ammunitionFilter.Caliber = &v } + + filter = ammunitionFilter case item.KindMagazine: - err = filter.AddString("caliber", r.URL.Query().Get("caliber")) - if err != nil { - break + magazineFilter := &item.MagazineFilter{} + + if v := r.URL.Query().Get("caliber"); v != "" { + if !isAllowedQueryChars(v) { + break + } + magazineFilter.Caliber = &v } + + filter = magazineFilter case item.KindMedical, item.KindFood, item.KindGrenade, item.KindClothing, item.KindModificationMuzzle, item.KindModificationDevice, item.KindModificationSight, item.KindModificationSightSpecial, item.KindModificationGoggles: - err = filter.AddString("type", r.URL.Query().Get("type")) - if err != nil { - break + customFilter := bson.D{} + + if v := r.URL.Query().Get("type"); v != "" { + if !isAllowedQueryChars(v) { + break + } + customFilter = append(customFilter, bson.E{Key: "type", Value: v}) } + + filter = &model.CustomFilter{D: customFilter} } if err != nil { StatusBadRequest(fmt.Sprintf("Query string error: %s", err)).Render(w) return } - result, err = item.GetAll(filter, kind, opts) + result, err = item.GetAll(filter.Filter(), kind, opts) if err != nil { handleError(err, w) return diff --git a/model/item/item.go b/model/item/item.go index dbb4296..91c2d06 100644 --- a/model/item/item.go +++ b/model/item/item.go @@ -116,9 +116,11 @@ func getManyByFilter(filter interface{}, k Kind, opts *Options) (*model.Result, } // GetAll returns a result based on filters -func GetAll(filter map[string]interface{}, k Kind, opts *Options) (*model.Result, error) { - filter["_kind"] = k - return getManyByFilter(filter, k, opts) +func GetAll(filter bson.D, k Kind, opts *Options) (*model.Result, error) { + f := bson.D{{Key: "_kind", Value: k}} + f = append(f, filter...) + + return getManyByFilter(f, k, opts) } // GetByIDs returns a result by given IDs diff --git a/model/item/kind_ammunition.go b/model/item/kind_ammunition.go index c7b620c..959f1a0 100644 --- a/model/item/kind_ammunition.go +++ b/model/item/kind_ammunition.go @@ -1,5 +1,7 @@ package item +import "go.mongodb.org/mongo-driver/bson" + const ( // KindAmmunition represents the kind of Ammunition KindAmmunition Kind = "ammunition" @@ -62,3 +64,28 @@ type AmmoGrenadeProperties struct { MinRadius float64 `json:"minRadius" bson:"minRadius"` MaxRadius float64 `json:"maxRadius" bson:"maxRadius"` } + +// AmmunitionFilter describes the filters used for filtering Ammunition +type AmmunitionFilter struct { + Caliber *string + Type *string +} + +// Filter implements the DocumentFilter interface +func (f *AmmunitionFilter) Filter() bson.D { + filters := []bson.M{} + + if f.Caliber != nil { + filters = append(filters, bson.M{"caliber": *f.Caliber}) + } + + if f.Type != nil { + filters = append(filters, bson.M{"type": *f.Type}) + } + + if len(filters) == 0 { + return bson.D{} + } + + return bson.D{{Key: "$and", Value: filters}} +} diff --git a/model/item/kind_armor.go b/model/item/kind_armor.go index 24c0475..7cfc97a 100644 --- a/model/item/kind_armor.go +++ b/model/item/kind_armor.go @@ -1,5 +1,7 @@ package item +import "go.mongodb.org/mongo-driver/bson" + const ( // KindArmor represents the kind of Armor KindArmor Kind = "armor" @@ -39,3 +41,39 @@ type ArmorMaterial struct { Name string `json:"name" bson:"name"` Destructibility float64 `json:"destructibility" bson:"destructibility"` } + +// ArmorFilter describes the filters used for filtering Armor +type ArmorFilter struct { + Type *string + ArmorClass *int64 + MaterialName *string +} + +// Filter implements the DocumentFilter interface +func (f *ArmorFilter) Filter() bson.D { + filters := []bson.M{} + + if f.Type != nil { + filters = append(filters, bson.M{"type": *f.Type}) + } + + if f.ArmorClass != nil { + filters = append(filters, bson.M{"$or": []bson.M{ + {"armor.class": *f.ArmorClass}, + {"components": bson.M{"$elemMatch": bson.M{"class": *f.ArmorClass}}}, + }}) + } + + if f.MaterialName != nil { + filters = append(filters, bson.M{"$or": []bson.M{ + {"armor.material.name": *f.MaterialName}, + {"components": bson.M{"$elemMatch": bson.M{"material.name": *f.MaterialName}}}, + }}) + } + + if len(filters) == 0 { + return bson.D{} + } + + return bson.D{{Key: "$and", Value: filters}} +} diff --git a/model/item/kind_firearm.go b/model/item/kind_firearm.go index 2db4b6a..c15ae45 100644 --- a/model/item/kind_firearm.go +++ b/model/item/kind_firearm.go @@ -1,5 +1,7 @@ package item +import "go.mongodb.org/mongo-driver/bson" + const ( // KindFirearm represents the kind of Firearm KindFirearm Kind = "firearm" @@ -34,3 +36,38 @@ type Firearm struct { CenterOfImpact float64 `json:"centerOfImpact" bson:"centerOfImpact"` Slots Slots `json:"slots" bson:"slots"` } + +// FirearmFilter describes the filters used for filtering Firearm +type FirearmFilter struct { + Manufacturer *string + Type *string + Class *string + Caliber *string +} + +// Filter implements the DocumentFilter interface +func (f *FirearmFilter) Filter() bson.D { + filters := []bson.M{} + + if f.Manufacturer != nil { + filters = append(filters, bson.M{"manufacturer": *f.Manufacturer}) + } + + if f.Type != nil { + filters = append(filters, bson.M{"type": *f.Type}) + } + + if f.Class != nil { + filters = append(filters, bson.M{"class": *f.Class}) + } + + if f.Caliber != nil { + filters = append(filters, bson.M{"caliber": *f.Caliber}) + } + + if len(filters) == 0 { + return bson.D{} + } + + return bson.D{{Key: "$and", Value: filters}} +} diff --git a/model/item/kind_magazine.go b/model/item/kind_magazine.go index 95922b1..01112e7 100644 --- a/model/item/kind_magazine.go +++ b/model/item/kind_magazine.go @@ -1,5 +1,7 @@ package item +import "go.mongodb.org/mongo-driver/bson" + const ( // KindMagazine represents the kind of Magazine KindMagazine Kind = "magazine" @@ -25,3 +27,23 @@ type MagazineModifier struct { CheckTime float64 `json:"checkTime" bson:"checkTime"` LoadUnload float64 `json:"loadUnload" bson:"loadUnload"` } + +// MagazineFilter describes the filters used for filtering Magazine +type MagazineFilter struct { + Caliber *string +} + +// Filter implements the DocumentFilter interface +func (f *MagazineFilter) Filter() bson.D { + filters := []bson.M{} + + if f.Caliber != nil { + filters = append(filters, bson.M{"caliber": *f.Caliber}) + } + + if len(filters) == 0 { + return bson.D{} + } + + return bson.D{{Key: "$and", Value: filters}} +} diff --git a/model/item/kind_tacticalRig.go b/model/item/kind_tacticalRig.go index 22dad22..6d8b37b 100644 --- a/model/item/kind_tacticalRig.go +++ b/model/item/kind_tacticalRig.go @@ -1,5 +1,7 @@ package item +import "go.mongodb.org/mongo-driver/bson" + const ( // KindTacticalrig represents the kind of TacticalRig KindTacticalrig Kind = "tacticalrig" @@ -17,3 +19,42 @@ type TacticalRig struct { IsPlateCarrier bool `json:"isPlateCarrier" bson:"isPlateCarrier"` Slots Slots `json:"slots" bson:"slots"` } + +// TacticalRigFilter describes the filters used for filtering TacticalRig +type TacticalRigFilter struct { + IsPlateCarrier *bool + IsArmored *bool + ArmorClass *int64 + ArmorMaterial *string +} + +// Filter implements the DocumentFilter interface +func (f *TacticalRigFilter) Filter() bson.D { + filters := []bson.M{} + + if f.IsPlateCarrier != nil { + filters = append(filters, bson.M{"isPlateCarrier": *f.IsPlateCarrier}) + } + + if f.IsArmored != nil { + filters = append(filters, bson.M{"armorComponents": bson.M{"$exists": *f.IsArmored}}) + } + + if f.ArmorClass != nil { + filters = append(filters, bson.M{ + "armorComponents": bson.M{"$elemMatch": bson.M{"class": *f.ArmorClass}}, + }) + } + + if f.ArmorMaterial != nil { + filters = append(filters, bson.M{ + "armorComponents": bson.M{"$elemMatch": bson.M{"material": *f.ArmorMaterial}}, + }) + } + + if len(filters) == 0 { + return bson.D{} + } + + return bson.D{{Key: "$and", Value: filters}} +} diff --git a/model/model.go b/model/model.go index 6df19b2..134be72 100644 --- a/model/model.go +++ b/model/model.go @@ -2,13 +2,10 @@ package model import ( "encoding/json" - "fmt" "net/http" - "net/url" - "regexp" - "strconv" "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -60,60 +57,17 @@ func NewResponse(msg string, code int) *Response { } } -// Filter represents an MongoDB query filter -type Filter map[string]interface{} - -var regexNotAllowedFieldChars = regexp.MustCompile(`[^[:alnum:][:blank:]!#%&'()*+,\-./:;?_~]`) - -// AddString adds a string to the given MongoDB field -func (f Filter) AddString(field, value string) error { - if value != "" { - if regexNotAllowedFieldChars.MatchString(value) { - return fmt.Errorf("%w: field \"%s\" contains invalid characters", ErrInvalidInput, field) - } - - var err error - f[field], err = url.QueryUnescape(value) - if err != nil { - return err - } - } - - return nil +// DocumentFilter describes the interface for filtering documents +type DocumentFilter interface { + Filter() bson.D } -// AddInt adds an integer to the given MongoDB field -func (f Filter) AddInt(field, value string) error { - if value != "" { - var err error - value, err = url.QueryUnescape(value) - if err != nil { - return err - } - - f[field], err = strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - } - - return nil +// CustomFilter describes a custom filter +type CustomFilter struct { + bson.D } -// AddFloat adds a float to the given MongoDB field -func (f Filter) AddFloat(field, value string) error { - if value != "" { - var err error - value, err = url.QueryUnescape(value) - if err != nil { - return err - } - - f[field], err = strconv.ParseFloat(value, 64) - if err != nil { - return err - } - } - - return nil +// Filter implements the DocumentFilter interface +func (f *CustomFilter) Filter() bson.D { + return f.D }