diff --git a/backend/internal/api/middleware/filters.go b/backend/internal/api/middleware/filters.go index 885defe..21c9518 100644 --- a/backend/internal/api/middleware/filters.go +++ b/backend/internal/api/middleware/filters.go @@ -5,12 +5,13 @@ import ( "encoding/json" "fmt" "net/http" + "strings" c "npm/internal/api/context" h "npm/internal/api/http" + "npm/internal/entity" "npm/internal/model" "npm/internal/util" - "strings" "github.com/qri-io/jsonschema" ) @@ -19,7 +20,9 @@ import ( // passed in to this endpoint. This will ensure that the filters are not injecting SQL. // After we have determined what the Filters are to be, they are saved on the Context // to be used later in other endpoints. -func Filters(schemaData string) func(http.Handler) http.Handler { +func Filters(obj interface{}) func(http.Handler) http.Handler { + schemaData := entity.GetFilterSchema(obj, true) + reservedFilterKeys := []string{ "limit", "offset", @@ -93,9 +96,10 @@ func Filters(schemaData string) func(http.Handler) http.Handler { return } + // todo: populate filters object with the gorm database name + ctx = context.WithValue(ctx, c.FiltersCtxKey, filters) next.ServeHTTP(w, r.WithContext(ctx)) - } else { next.ServeHTTP(w, r) } @@ -108,8 +112,7 @@ func GetFiltersFromContext(r *http.Request) []model.Filter { filters, ok := r.Context().Value(c.FiltersCtxKey).([]model.Filter) if !ok { // the assertion failed - var emptyFilters []model.Filter - return emptyFilters + return nil } return filters } diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index 87700dd..2ef13e1 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -8,6 +8,14 @@ import ( "npm/internal/api/middleware" "npm/internal/api/schema" "npm/internal/config" + "npm/internal/entity/accesslist" + "npm/internal/entity/certificate" + "npm/internal/entity/certificateauthority" + "npm/internal/entity/dnsprovider" + "npm/internal/entity/host" + "npm/internal/entity/nginxtemplate" + "npm/internal/entity/stream" + "npm/internal/entity/upstream" "npm/internal/entity/user" "npm/internal/logger" "npm/internal/serverevents" @@ -93,9 +101,10 @@ func applyRoutes(r chi.Router) chi.Router { r.With(middleware.Enforce(user.CapabilityUsersManage)).Route("/", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityUsersManage), middleware.Filters(user.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityUsersManage)). - Get("/", handler.GetUsers()) + r.With( + middleware.Enforce(user.CapabilityUsersManage), + middleware.Filters(user.Model{}), + ).Get("/", handler.GetUsers()) // Specific Item r.Get("/{userID:[0-9]+}", handler.GetUser()) @@ -136,9 +145,10 @@ func applyRoutes(r chi.Router) chi.Router { // Access Lists r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) { // List - // r.With(middleware.Filters(accesslist.GetFilterSchema()), middleware.Enforce(user.CapabilityAccessListsView)). - r.With(middleware.Enforce(user.CapabilityAccessListsView)). - Get("/", handler.GetAccessLists()) + r.With( + middleware.Enforce(user.CapabilityAccessListsView), + middleware.Filters(accesslist.Model{}), + ).Get("/", handler.GetAccessLists()) // Create r.With(middleware.Enforce(user.CapabilityAccessListsManage), middleware.EnforceRequestSchema(schema.CreateAccessList())). @@ -159,9 +169,10 @@ func applyRoutes(r chi.Router) chi.Router { // DNS Providers r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityDNSProvidersView), middleware.Filters(dnsprovider.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityDNSProvidersView)). - Get("/", handler.GetDNSProviders()) + r.With( + middleware.Enforce(user.CapabilityDNSProvidersView), + middleware.Filters(dnsprovider.Model{}), + ).Get("/", handler.GetDNSProviders()) // Create r.With(middleware.Enforce(user.CapabilityDNSProvidersManage), middleware.EnforceRequestSchema(schema.CreateDNSProvider())). @@ -188,9 +199,10 @@ func applyRoutes(r chi.Router) chi.Router { // Certificate Authorities r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView), middleware.Filters(certificateauthority.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView)). - Get("/", handler.GetCertificateAuthorities()) + r.With( + middleware.Enforce(user.CapabilityCertificateAuthoritiesView), + middleware.Filters(certificateauthority.Model{}), + ).Get("/", handler.GetCertificateAuthorities()) // Create r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage), middleware.EnforceRequestSchema(schema.CreateCertificateAuthority())). @@ -217,9 +229,10 @@ func applyRoutes(r chi.Router) chi.Router { // Certificates r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityCertificatesView), middleware.Filters(certificate.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityCertificatesView)). - Get("/", handler.GetCertificates()) + r.With( + middleware.Enforce(user.CapabilityCertificatesView), + middleware.Filters(certificate.Model{}), + ).Get("/", handler.GetCertificates()) // Create r.With(middleware.Enforce(user.CapabilityCertificatesManage), middleware.EnforceRequestSchema(schema.CreateCertificate())). @@ -243,9 +256,10 @@ func applyRoutes(r chi.Router) chi.Router { // Hosts r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(host.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityHostsView)). - Get("/", handler.GetHosts()) + r.With( + middleware.Enforce(user.CapabilityHostsView), + middleware.Filters(host.Model{}), + ).Get("/", handler.GetHosts()) // Create r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateHost())). @@ -268,9 +282,10 @@ func applyRoutes(r chi.Router) chi.Router { // Nginx Templates r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityNginxTemplatesView), middleware.Filters(nginxtemplate.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityNginxTemplatesView)). - Get("/", handler.GetNginxTemplates()) + r.With( + middleware.Enforce(user.CapabilityNginxTemplatesView), + middleware.Filters(nginxtemplate.Model{}), + ).Get("/", handler.GetNginxTemplates()) // Create r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage), middleware.EnforceRequestSchema(schema.CreateNginxTemplate())). @@ -291,9 +306,10 @@ func applyRoutes(r chi.Router) chi.Router { // Streams r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityStreamsView), middleware.Filters(stream.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityStreamsView)). - Get("/", handler.GetStreams()) + r.With( + middleware.Enforce(user.CapabilityStreamsView), + middleware.Filters(stream.Model{}), + ).Get("/", handler.GetStreams()) // Create r.With(middleware.Enforce(user.CapabilityStreamsManage), middleware.EnforceRequestSchema(schema.CreateStream())). @@ -314,9 +330,10 @@ func applyRoutes(r chi.Router) chi.Router { // Upstreams r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) { // List - // r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(upstream.GetFilterSchema())). - r.With(middleware.Enforce(user.CapabilityHostsView)). - Get("/", handler.GetUpstreams()) + r.With( + middleware.Enforce(user.CapabilityHostsView), + middleware.Filters(upstream.Model{}), + ).Get("/", handler.GetUpstreams()) // Create r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateUpstream())). diff --git a/backend/internal/entity/accesslist/methods.go b/backend/internal/entity/accesslist/methods.go index b75dc19..f86d9bb 100644 --- a/backend/internal/entity/accesslist/methods.go +++ b/backend/internal/entity/accesslist/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/certificate/methods.go b/backend/internal/entity/certificate/methods.go index 10bf53a..676135f 100644 --- a/backend/internal/entity/certificate/methods.go +++ b/backend/internal/entity/certificate/methods.go @@ -37,7 +37,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/certificateauthority/methods.go b/backend/internal/entity/certificateauthority/methods.go index 69e19b5..47b40f1 100644 --- a/backend/internal/entity/certificateauthority/methods.go +++ b/backend/internal/entity/certificateauthority/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/dnsprovider/methods.go b/backend/internal/entity/dnsprovider/methods.go index fdd3dc5..95f037e 100644 --- a/backend/internal/entity/dnsprovider/methods.go +++ b/backend/internal/entity/dnsprovider/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/filters.go b/backend/internal/entity/filters.go index e3a8c01..317d275 100644 --- a/backend/internal/entity/filters.go +++ b/backend/internal/entity/filters.go @@ -1,99 +1,28 @@ package entity import ( - "fmt" "reflect" + "regexp" "strings" - - "npm/internal/model" ) -// FilterMapFunction is a filter map function -type FilterMapFunction func(value []string) []string - -// GenerateSQLFromFilters will return a Query and params for use as WHERE clause in SQL queries -// This will use a AND where clause approach. -func GenerateSQLFromFilters(filters []model.Filter, fieldMap map[string]string, fieldMapFunctions map[string]FilterMapFunction) (string, []interface{}) { - clauses := make([]string, 0) - params := make([]interface{}, 0) - - for _, filter := range filters { - // Lookup this filter field from the functions map - if _, ok := fieldMapFunctions[filter.Field]; ok { - filter.Value = fieldMapFunctions[filter.Field](filter.Value) - } - - // Lookup this filter field from the name map - if _, ok := fieldMap[filter.Field]; ok { - filter.Field = fieldMap[filter.Field] - } - - // Special case for LIKE queries, the column needs to be uppercase for comparison - fieldName := fmt.Sprintf("`%s`", filter.Field) - if strings.ToLower(filter.Modifier) == "contains" || strings.ToLower(filter.Modifier) == "starts" || strings.ToLower(filter.Modifier) == "ends" { - fieldName = fmt.Sprintf("UPPER(`%s`)", filter.Field) - } - - clauses = append(clauses, fmt.Sprintf("%s %s", fieldName, getSQLAssignmentFromModifier(filter, ¶ms))) - } - - return strings.Join(clauses, " AND "), params +type filterMapValue struct { + Type string + Field string } -func getSQLAssignmentFromModifier(filter model.Filter, params *[]interface{}) string { - var clause string - - // Quick hacks - if filter.Modifier == "in" && len(filter.Value) == 1 { - filter.Modifier = "equals" - } else if filter.Modifier == "notin" && len(filter.Value) == 1 { - filter.Modifier = "not" - } - - switch strings.ToLower(filter.Modifier) { - default: - clause = "= ?" - case "not": - clause = "!= ?" - case "min": - clause = ">= ?" - case "max": - clause = "<= ?" - case "greater": - clause = "> ?" - case "lesser": - clause = "< ?" - - // LIKE modifiers: - case "contains": - *params = append(*params, strings.ToUpper(filter.Value[0])) - return "LIKE '%' || ? || '%'" - case "starts": - *params = append(*params, strings.ToUpper(filter.Value[0])) - return "LIKE ? || '%'" - case "ends": - *params = append(*params, strings.ToUpper(filter.Value[0])) - return "LIKE '%' || ?" - - // Array parameter modifiers: - case "in": - s, p := buildInArray(filter.Value) - *params = append(*params, p...) - return fmt.Sprintf("IN (%s)", s) - case "notin": - s, p := buildInArray(filter.Value) - *params = append(*params, p...) - return fmt.Sprintf("NOT IN (%s)", s) - } - - *params = append(*params, filter.Value[0]) - return clause -} - -/* // GetFilterMap returns the filter map -func GetFilterMap(m interface{}) map[string]string { - var filterMap = make(map[string]string) +func GetFilterMap(m interface{}, includeBaseEntity bool) map[string]filterMapValue { + filterMap := getFilterMapForInterface(m) + if includeBaseEntity { + return mergeFilterMaps(getFilterMapForInterface(ModelBase{}), filterMap) + } + + return filterMap +} + +func getFilterMapForInterface(m interface{}) map[string]filterMapValue { + var filterMap = make(map[string]filterMapValue) // TypeOf returns the reflection Type that represents the dynamic type of variable. // If variable is a nil interface value, TypeOf returns nil. @@ -106,49 +35,37 @@ func GetFilterMap(m interface{}) map[string]string { // Get the field tag value filterTag := field.Tag.Get("filter") - dbTag := field.Tag.Get("db") + dbTag := field.Tag.Get("gorm") if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" { + // db can have many parts, we need to pull out the "column:value" part + dbField := field.Name + r := regexp.MustCompile(`(?:^|;)column:([^;|$]+)(?:$|;)`) + if matches := r.FindStringSubmatch(dbTag); len(matches) > 1 { + dbField = matches[1] + } // Filter tag can be a 2 part thing: name,type // ie: account_id,integer // So we need to split and use the first part parts := strings.Split(filterTag, ",") - filterMap[parts[0]] = dbTag - filterMap[filterTag] = dbTag + if len(parts) > 1 { + filterMap[parts[0]] = filterMapValue{ + Type: parts[1], + Field: dbField, + } + } } } return filterMap } -*/ -// GetDBColumns returns the db columns -func GetDBColumns(m interface{}) []string { - var columns []string - t := reflect.TypeOf(m) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - dbTag := field.Tag.Get("db") - if dbTag != "" && dbTag != "-" { - columns = append(columns, dbTag) - } +func mergeFilterMaps(m1 map[string]filterMapValue, m2 map[string]filterMapValue) map[string]filterMapValue { + merged := make(map[string]filterMapValue, 0) + for k, v := range m1 { + merged[k] = v } - - return columns -} - -func buildInArray(items []string) (string, []interface{}) { - // Query string placeholder - strs := make([]string, len(items)) - for i := 0; i < len(items); i++ { - strs[i] = "?" - } - - // Params as interface - params := make([]interface{}, len(items)) - for i, v := range items { - params[i] = v - } - - return strings.Join(strs, ", "), params + for key, value := range m2 { + merged[key] = value + } + return merged } diff --git a/backend/internal/entity/filters_schema.go b/backend/internal/entity/filters_schema.go index facc8c8..059df6f 100644 --- a/backend/internal/entity/filters_schema.go +++ b/backend/internal/entity/filters_schema.go @@ -2,52 +2,80 @@ package entity import ( "fmt" + "npm/internal/logger" + "npm/internal/util" "reflect" "strings" + + "github.com/rotisserie/eris" ) // GetFilterSchema creates a jsonschema for validating filters, based on the model // object given and by reading the struct "filter" tags. -func GetFilterSchema(m interface{}) string { +func GetFilterSchema(m interface{}, includeBaseEntity bool) string { var schemas []string t := reflect.TypeOf(m) + if t.Kind() != reflect.Struct { + logger.Error("GetFilterSchemaError", eris.Errorf("%v type can't have attributes inspected", t.Kind())) + return "" + } + + // The base entity model + if includeBaseEntity { + b := reflect.TypeOf(ModelBase{}) + for i := 0; i < b.NumField(); i++ { + bField := b.Field(i) + bFilterTag := bField.Tag.Get("filter") + if bFilterTag != "" && bFilterTag != "-" { + schemas = append(schemas, getFilterTagSchema(bFilterTag)) + } + } + } + + // The actual interface for i := 0; i < t.NumField(); i++ { field := t.Field(i) filterTag := field.Tag.Get("filter") if filterTag != "" && filterTag != "-" { - // split out tag value "field,filtreType" - // with a default filter type of string - items := strings.Split(filterTag, ",") - if len(items) == 1 { - items = append(items, "string") - } - - switch items[1] { - case "int": - fallthrough - case "integer": - schemas = append(schemas, intFieldSchema(items[0])) - case "bool": - fallthrough - case "boolean": - schemas = append(schemas, boolFieldSchema(items[0])) - case "date": - schemas = append(schemas, dateFieldSchema(items[0])) - case "regex": - if len(items) < 3 { - items = append(items, ".*") - } - schemas = append(schemas, regexFieldSchema(items[0], items[2])) - - default: - schemas = append(schemas, stringFieldSchema(items[0])) - } + schemas = append(schemas, getFilterTagSchema(filterTag)) } } - return newFilterSchema(schemas) + return util.PrettyPrintJSON(newFilterSchema(schemas)) +} + +func getFilterTagSchema(filterTag string) string { + // split out tag value "field,filtreType" + // with a default filter type of string + items := strings.Split(filterTag, ",") + if len(items) == 1 { + items = append(items, "string") + } + + switch items[1] { + case "number": + fallthrough + case "int": + fallthrough + case "integer": + return intFieldSchema(items[0]) + case "bool": + fallthrough + case "boolean": + return boolFieldSchema(items[0]) + case "date": + return dateFieldSchema(items[0]) + case "regex": + if len(items) < 3 { + items = append(items, ".*") + } + return regexFieldSchema(items[0], items[2]) + + default: + return stringFieldSchema(items[0]) + } } // newFilterSchema is the main method to specify a new Filter Schema for use in Middleware diff --git a/backend/internal/entity/host/methods.go b/backend/internal/entity/host/methods.go index 59219be..6769ed8 100644 --- a/backend/internal/entity/host/methods.go +++ b/backend/internal/entity/host/methods.go @@ -23,7 +23,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/lists.go b/backend/internal/entity/lists.go index 24cf5ea..f3fbb7c 100644 --- a/backend/internal/entity/lists.go +++ b/backend/internal/entity/lists.go @@ -27,10 +27,11 @@ func ListQueryBuilder( pageInfo *model.PageInfo, defaultSort model.Sort, filters []model.Filter, + filterMap map[string]filterMapValue, ) *gorm.DB { scopes := make([]func(*gorm.DB) *gorm.DB, 0) scopes = append(scopes, ScopeOrderBy(pageInfo, defaultSort)) scopes = append(scopes, ScopeOffsetLimit(pageInfo)) - // scopes = append(scopes, ScopeFilters(GetFilterMap(m))) + scopes = append(scopes, ScopeFilters(filters, filterMap)) return database.GetDB().Scopes(scopes...) } diff --git a/backend/internal/entity/model_base.go b/backend/internal/entity/model_base.go index f3de818..0050ab7 100644 --- a/backend/internal/entity/model_base.go +++ b/backend/internal/entity/model_base.go @@ -6,8 +6,8 @@ import ( // ModelBase include common fields for db control type ModelBase struct { - ID uint `json:"id" gorm:"column:id;primaryKey"` - CreatedAt int64 `json:"created_at" gorm:"<-:create;autoCreateTime:milli;column:created_at"` - UpdatedAt int64 `json:"updated_at" gorm:"<-;autoUpdateTime:milli;column:updated_at"` + ID uint `json:"id" gorm:"column:id;primaryKey" filter:"id,integer"` + CreatedAt int64 `json:"created_at" gorm:"<-:create;autoCreateTime:milli;column:created_at" filter:"created_at,date"` + UpdatedAt int64 `json:"updated_at" gorm:"<-;autoUpdateTime:milli;column:updated_at" filter:"updated_at,date"` DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:is_deleted;softDelete:flag"` } diff --git a/backend/internal/entity/nginxtemplate/methods.go b/backend/internal/entity/nginxtemplate/methods.go index c851bf5..931f346 100644 --- a/backend/internal/entity/nginxtemplate/methods.go +++ b/backend/internal/entity/nginxtemplate/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/scopes.go b/backend/internal/entity/scopes.go index 4f5e00a..b4d6811 100644 --- a/backend/internal/entity/scopes.go +++ b/backend/internal/entity/scopes.go @@ -1,6 +1,7 @@ package entity import ( + "fmt" "strings" "npm/internal/model" @@ -34,17 +35,57 @@ func ScopeOrderBy(pageInfo *model.PageInfo, defaultSort model.Sort) func(db *gor } } -func ScopeFilters(filters map[string]string) func(db *gorm.DB) *gorm.DB { +func ScopeFilters(filters []model.Filter, filterMap map[string]filterMapValue) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { - // todo - /* - if filters != nil { - filterMap := GetFilterMap(m) - filterQuery, filterParams := GenerateSQLFromFilters(filters, filterMap, filterMapFunctions) - whereStrings = []string{filterQuery} - params = append(params, filterParams...) + for _, f := range filters { + // Lookup this filter field from the name map + if _, ok := filterMap[f.Field]; ok { + f.Field = filterMap[f.Field].Field } - */ + + // For boolean fields, the value needs tweaking + if filterMap[f.Field].Type == "boolean" { + f.Value = parseBoolValue(f.Value[0]) + } + + // Quick adjustments for commonalities + if f.Modifier == "in" && len(f.Value) == 1 { + f.Modifier = "equals" + } else if f.Modifier == "notin" && len(f.Value) == 1 { + f.Modifier = "not" + } + + switch strings.ToLower(f.Modifier) { + case "not": + db.Where(fmt.Sprintf("%s != ?", f.Field), f.Value) + case "min": + db.Where(fmt.Sprintf("%s >= ?", f.Field), f.Value) + case "max": + db.Where(fmt.Sprintf("%s <= ?", f.Field), f.Value) + case "greater": + db.Where(fmt.Sprintf("%s > ?", f.Field), f.Value) + case "lesser": + db.Where(fmt.Sprintf("%s < ?", f.Field), f.Value) + + // LIKE modifiers: + case "contains": + db.Where(fmt.Sprintf("%s LIKE ?", f.Field), `%`+f.Value[0]+`%`) + case "starts": + db.Where(fmt.Sprintf("%s LIKE ?", f.Field), f.Value[0]+`%`) + case "ends": + db.Where(fmt.Sprintf("%s LIKE ?", f.Field), `%`+f.Value[0]) + + // Array parameter modifiers: + case "in": + db.Where(fmt.Sprintf("%s IN ?", f.Field), f.Value) + case "notin": + db.Where(fmt.Sprintf("%s NOT IN ?", f.Field), f.Value) + + // Default: equals + default: + db.Where(fmt.Sprintf("%s = ?", f.Field), f.Value) + } + } return db } } @@ -60,3 +101,22 @@ func sortToOrderString(sorts []model.Sort) string { } return strings.Join(strs, ", ") } + +func parseBoolValue(v string) []string { + bVal := "0" + switch strings.ToLower(v) { + case "yes": + fallthrough + case "true": + fallthrough + case "on": + fallthrough + case "t": + fallthrough + case "1": + fallthrough + case "y": + bVal = "1" + } + return []string{bVal} +} diff --git a/backend/internal/entity/setting/methods.go b/backend/internal/entity/setting/methods.go index 2c541d4..082c726 100644 --- a/backend/internal/entity/setting/methods.go +++ b/backend/internal/entity/setting/methods.go @@ -30,7 +30,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/stream/methods.go b/backend/internal/entity/stream/methods.go index b153bd1..c9cb259 100644 --- a/backend/internal/entity/stream/methods.go +++ b/backend/internal/entity/stream/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/upstream/methods.go b/backend/internal/entity/upstream/methods.go index 13870fc..d1a5f64 100644 --- a/backend/internal/entity/upstream/methods.go +++ b/backend/internal/entity/upstream/methods.go @@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/upstreamserver/methods.go b/backend/internal/entity/upstreamserver/methods.go index 776ab5c..1ffe309 100644 --- a/backend/internal/entity/upstreamserver/methods.go +++ b/backend/internal/entity/upstreamserver/methods.go @@ -30,7 +30,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse, Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/user/methods.go b/backend/internal/entity/user/methods.go index 37b3b2d..03b097b 100644 --- a/backend/internal/entity/user/methods.go +++ b/backend/internal/entity/user/methods.go @@ -41,7 +41,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent Direction: "ASC", } - dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters) + dbo := entity.ListQueryBuilder(&pageInfo, defaultSort, filters, entity.GetFilterMap(Model{}, true)) // Get count of items in this search var totalRows int64 diff --git a/backend/internal/entity/user/model.go b/backend/internal/entity/user/model.go index 418cffa..39c956e 100644 --- a/backend/internal/entity/user/model.go +++ b/backend/internal/entity/user/model.go @@ -20,7 +20,7 @@ type Model struct { Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"` Email string `json:"email" gorm:"column:email" filter:"email,email"` IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"` - IsSystem bool `json:"is_system,omitempty" gorm:"column:is_system"` + IsSystem bool `json:"is_system,omitempty" gorm:"column:is_system" filter:"is_system,boolean"` // Other GravatarURL string `json:"gravatar_url" gorm:"-"` // Expansions diff --git a/backend/internal/util/strings.go b/backend/internal/util/strings.go index 085602b..1e4f0a5 100644 --- a/backend/internal/util/strings.go +++ b/backend/internal/util/strings.go @@ -1,6 +1,9 @@ package util import ( + "bytes" + "encoding/json" + "npm/internal/logger" "regexp" "strings" "unicode" @@ -22,3 +25,16 @@ func CleanupWhitespace(s string) string { return result } + +// PrettyPrintJSON takes a string and as long as it's JSON, +// it will return a pretty printed and formatted version +func PrettyPrintJSON(s string) string { + byt := []byte(s) + var prettyJSON bytes.Buffer + if err := json.Indent(&prettyJSON, byt, "", " "); err != nil { + logger.Debug("Can't pretty print non-json string: %s", s) + return s + } + + return prettyJSON.String() +}