Add entity filters back in for api

This commit is contained in:
Jamie Curnow 2023-05-29 13:53:16 +10:00
parent 1ae247b2a6
commit 4b39ef0eba
No known key found for this signature in database
GPG Key ID: FFBB624C43388E9E
20 changed files with 247 additions and 205 deletions

View File

@ -5,12 +5,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
c "npm/internal/api/context" c "npm/internal/api/context"
h "npm/internal/api/http" h "npm/internal/api/http"
"npm/internal/entity"
"npm/internal/model" "npm/internal/model"
"npm/internal/util" "npm/internal/util"
"strings"
"github.com/qri-io/jsonschema" "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. // 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 // After we have determined what the Filters are to be, they are saved on the Context
// to be used later in other endpoints. // 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{ reservedFilterKeys := []string{
"limit", "limit",
"offset", "offset",
@ -93,9 +96,10 @@ func Filters(schemaData string) func(http.Handler) http.Handler {
return return
} }
// todo: populate filters object with the gorm database name
ctx = context.WithValue(ctx, c.FiltersCtxKey, filters) ctx = context.WithValue(ctx, c.FiltersCtxKey, filters)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
} else { } else {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
} }
@ -108,8 +112,7 @@ func GetFiltersFromContext(r *http.Request) []model.Filter {
filters, ok := r.Context().Value(c.FiltersCtxKey).([]model.Filter) filters, ok := r.Context().Value(c.FiltersCtxKey).([]model.Filter)
if !ok { if !ok {
// the assertion failed // the assertion failed
var emptyFilters []model.Filter return nil
return emptyFilters
} }
return filters return filters
} }

View File

@ -8,6 +8,14 @@ import (
"npm/internal/api/middleware" "npm/internal/api/middleware"
"npm/internal/api/schema" "npm/internal/api/schema"
"npm/internal/config" "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/entity/user"
"npm/internal/logger" "npm/internal/logger"
"npm/internal/serverevents" "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) { r.With(middleware.Enforce(user.CapabilityUsersManage)).Route("/", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityUsersManage), middleware.Filters(user.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityUsersManage)). middleware.Enforce(user.CapabilityUsersManage),
Get("/", handler.GetUsers()) middleware.Filters(user.Model{}),
).Get("/", handler.GetUsers())
// Specific Item // Specific Item
r.Get("/{userID:[0-9]+}", handler.GetUser()) r.Get("/{userID:[0-9]+}", handler.GetUser())
@ -136,9 +145,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Access Lists // Access Lists
r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/access-lists", func(r chi.Router) {
// List // List
// r.With(middleware.Filters(accesslist.GetFilterSchema()), middleware.Enforce(user.CapabilityAccessListsView)). r.With(
r.With(middleware.Enforce(user.CapabilityAccessListsView)). middleware.Enforce(user.CapabilityAccessListsView),
Get("/", handler.GetAccessLists()) middleware.Filters(accesslist.Model{}),
).Get("/", handler.GetAccessLists())
// Create // Create
r.With(middleware.Enforce(user.CapabilityAccessListsManage), middleware.EnforceRequestSchema(schema.CreateAccessList())). r.With(middleware.Enforce(user.CapabilityAccessListsManage), middleware.EnforceRequestSchema(schema.CreateAccessList())).
@ -159,9 +169,10 @@ func applyRoutes(r chi.Router) chi.Router {
// DNS Providers // DNS Providers
r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/dns-providers", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityDNSProvidersView), middleware.Filters(dnsprovider.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityDNSProvidersView)). middleware.Enforce(user.CapabilityDNSProvidersView),
Get("/", handler.GetDNSProviders()) middleware.Filters(dnsprovider.Model{}),
).Get("/", handler.GetDNSProviders())
// Create // Create
r.With(middleware.Enforce(user.CapabilityDNSProvidersManage), middleware.EnforceRequestSchema(schema.CreateDNSProvider())). r.With(middleware.Enforce(user.CapabilityDNSProvidersManage), middleware.EnforceRequestSchema(schema.CreateDNSProvider())).
@ -188,9 +199,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Certificate Authorities // Certificate Authorities
r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/certificate-authorities", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView), middleware.Filters(certificateauthority.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesView)). middleware.Enforce(user.CapabilityCertificateAuthoritiesView),
Get("/", handler.GetCertificateAuthorities()) middleware.Filters(certificateauthority.Model{}),
).Get("/", handler.GetCertificateAuthorities())
// Create // Create
r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage), middleware.EnforceRequestSchema(schema.CreateCertificateAuthority())). r.With(middleware.Enforce(user.CapabilityCertificateAuthoritiesManage), middleware.EnforceRequestSchema(schema.CreateCertificateAuthority())).
@ -217,9 +229,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Certificates // Certificates
r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/certificates", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityCertificatesView), middleware.Filters(certificate.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityCertificatesView)). middleware.Enforce(user.CapabilityCertificatesView),
Get("/", handler.GetCertificates()) middleware.Filters(certificate.Model{}),
).Get("/", handler.GetCertificates())
// Create // Create
r.With(middleware.Enforce(user.CapabilityCertificatesManage), middleware.EnforceRequestSchema(schema.CreateCertificate())). r.With(middleware.Enforce(user.CapabilityCertificatesManage), middleware.EnforceRequestSchema(schema.CreateCertificate())).
@ -243,9 +256,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Hosts // Hosts
r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/hosts", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(host.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityHostsView)). middleware.Enforce(user.CapabilityHostsView),
Get("/", handler.GetHosts()) middleware.Filters(host.Model{}),
).Get("/", handler.GetHosts())
// Create // Create
r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateHost())). r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateHost())).
@ -268,9 +282,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Nginx Templates // Nginx Templates
r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/nginx-templates", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityNginxTemplatesView), middleware.Filters(nginxtemplate.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityNginxTemplatesView)). middleware.Enforce(user.CapabilityNginxTemplatesView),
Get("/", handler.GetNginxTemplates()) middleware.Filters(nginxtemplate.Model{}),
).Get("/", handler.GetNginxTemplates())
// Create // Create
r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage), middleware.EnforceRequestSchema(schema.CreateNginxTemplate())). r.With(middleware.Enforce(user.CapabilityNginxTemplatesManage), middleware.EnforceRequestSchema(schema.CreateNginxTemplate())).
@ -291,9 +306,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Streams // Streams
r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/streams", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityStreamsView), middleware.Filters(stream.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityStreamsView)). middleware.Enforce(user.CapabilityStreamsView),
Get("/", handler.GetStreams()) middleware.Filters(stream.Model{}),
).Get("/", handler.GetStreams())
// Create // Create
r.With(middleware.Enforce(user.CapabilityStreamsManage), middleware.EnforceRequestSchema(schema.CreateStream())). r.With(middleware.Enforce(user.CapabilityStreamsManage), middleware.EnforceRequestSchema(schema.CreateStream())).
@ -314,9 +330,10 @@ func applyRoutes(r chi.Router) chi.Router {
// Upstreams // Upstreams
r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) { r.With(middleware.EnforceSetup(true)).Route("/upstreams", func(r chi.Router) {
// List // List
// r.With(middleware.Enforce(user.CapabilityHostsView), middleware.Filters(upstream.GetFilterSchema())). r.With(
r.With(middleware.Enforce(user.CapabilityHostsView)). middleware.Enforce(user.CapabilityHostsView),
Get("/", handler.GetUpstreams()) middleware.Filters(upstream.Model{}),
).Get("/", handler.GetUpstreams())
// Create // Create
r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateUpstream())). r.With(middleware.Enforce(user.CapabilityHostsManage), middleware.EnforceRequestSchema(schema.CreateUpstream())).

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -37,7 +37,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -1,99 +1,28 @@
package entity package entity
import ( import (
"fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"npm/internal/model"
) )
// FilterMapFunction is a filter map function type filterMapValue struct {
type FilterMapFunction func(value []string) []string Type string
Field 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, &params)))
}
return strings.Join(clauses, " AND "), params
} }
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 // GetFilterMap returns the filter map
func GetFilterMap(m interface{}) map[string]string { func GetFilterMap(m interface{}, includeBaseEntity bool) map[string]filterMapValue {
var filterMap = make(map[string]string) 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. // TypeOf returns the reflection Type that represents the dynamic type of variable.
// If variable is a nil interface value, TypeOf returns nil. // 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 // Get the field tag value
filterTag := field.Tag.Get("filter") filterTag := field.Tag.Get("filter")
dbTag := field.Tag.Get("db") dbTag := field.Tag.Get("gorm")
if filterTag != "" && dbTag != "" && dbTag != "-" && filterTag != "-" { 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 // Filter tag can be a 2 part thing: name,type
// ie: account_id,integer // ie: account_id,integer
// So we need to split and use the first part // So we need to split and use the first part
parts := strings.Split(filterTag, ",") parts := strings.Split(filterTag, ",")
filterMap[parts[0]] = dbTag if len(parts) > 1 {
filterMap[filterTag] = dbTag filterMap[parts[0]] = filterMapValue{
Type: parts[1],
Field: dbField,
}
}
} }
} }
return filterMap return filterMap
} }
*/
// GetDBColumns returns the db columns func mergeFilterMaps(m1 map[string]filterMapValue, m2 map[string]filterMapValue) map[string]filterMapValue {
func GetDBColumns(m interface{}) []string { merged := make(map[string]filterMapValue, 0)
var columns []string for k, v := range m1 {
t := reflect.TypeOf(m) merged[k] = v
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
dbTag := field.Tag.Get("db")
if dbTag != "" && dbTag != "-" {
columns = append(columns, dbTag)
} }
for key, value := range m2 {
merged[key] = value
} }
return merged
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
} }

View File

@ -2,21 +2,51 @@ package entity
import ( import (
"fmt" "fmt"
"npm/internal/logger"
"npm/internal/util"
"reflect" "reflect"
"strings" "strings"
"github.com/rotisserie/eris"
) )
// GetFilterSchema creates a jsonschema for validating filters, based on the model // GetFilterSchema creates a jsonschema for validating filters, based on the model
// object given and by reading the struct "filter" tags. // object given and by reading the struct "filter" tags.
func GetFilterSchema(m interface{}) string { func GetFilterSchema(m interface{}, includeBaseEntity bool) string {
var schemas []string var schemas []string
t := reflect.TypeOf(m) 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++ { for i := 0; i < t.NumField(); i++ {
field := t.Field(i) field := t.Field(i)
filterTag := field.Tag.Get("filter") filterTag := field.Tag.Get("filter")
if filterTag != "" && filterTag != "-" { if filterTag != "" && filterTag != "-" {
schemas = append(schemas, getFilterTagSchema(filterTag))
}
}
return util.PrettyPrintJSON(newFilterSchema(schemas))
}
func getFilterTagSchema(filterTag string) string {
// split out tag value "field,filtreType" // split out tag value "field,filtreType"
// with a default filter type of string // with a default filter type of string
items := strings.Split(filterTag, ",") items := strings.Split(filterTag, ",")
@ -25,29 +55,27 @@ func GetFilterSchema(m interface{}) string {
} }
switch items[1] { switch items[1] {
case "number":
fallthrough
case "int": case "int":
fallthrough fallthrough
case "integer": case "integer":
schemas = append(schemas, intFieldSchema(items[0])) return intFieldSchema(items[0])
case "bool": case "bool":
fallthrough fallthrough
case "boolean": case "boolean":
schemas = append(schemas, boolFieldSchema(items[0])) return boolFieldSchema(items[0])
case "date": case "date":
schemas = append(schemas, dateFieldSchema(items[0])) return dateFieldSchema(items[0])
case "regex": case "regex":
if len(items) < 3 { if len(items) < 3 {
items = append(items, ".*") items = append(items, ".*")
} }
schemas = append(schemas, regexFieldSchema(items[0], items[2])) return regexFieldSchema(items[0], items[2])
default: default:
schemas = append(schemas, stringFieldSchema(items[0])) return stringFieldSchema(items[0])
} }
}
}
return newFilterSchema(schemas)
} }
// newFilterSchema is the main method to specify a new Filter Schema for use in Middleware // newFilterSchema is the main method to specify a new Filter Schema for use in Middleware

View File

@ -23,7 +23,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -27,10 +27,11 @@ func ListQueryBuilder(
pageInfo *model.PageInfo, pageInfo *model.PageInfo,
defaultSort model.Sort, defaultSort model.Sort,
filters []model.Filter, filters []model.Filter,
filterMap map[string]filterMapValue,
) *gorm.DB { ) *gorm.DB {
scopes := make([]func(*gorm.DB) *gorm.DB, 0) scopes := make([]func(*gorm.DB) *gorm.DB, 0)
scopes = append(scopes, ScopeOrderBy(pageInfo, defaultSort)) scopes = append(scopes, ScopeOrderBy(pageInfo, defaultSort))
scopes = append(scopes, ScopeOffsetLimit(pageInfo)) scopes = append(scopes, ScopeOffsetLimit(pageInfo))
// scopes = append(scopes, ScopeFilters(GetFilterMap(m))) scopes = append(scopes, ScopeFilters(filters, filterMap))
return database.GetDB().Scopes(scopes...) return database.GetDB().Scopes(scopes...)
} }

View File

@ -6,8 +6,8 @@ import (
// ModelBase include common fields for db control // ModelBase include common fields for db control
type ModelBase struct { type ModelBase struct {
ID uint `json:"id" gorm:"column:id;primaryKey"` ID uint `json:"id" gorm:"column:id;primaryKey" filter:"id,integer"`
CreatedAt int64 `json:"created_at" gorm:"<-:create;autoCreateTime:milli;column:created_at"` 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"` 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"` DeletedAt soft_delete.DeletedAt `json:"-" gorm:"column:is_deleted;softDelete:flag"`
} }

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -1,6 +1,7 @@
package entity package entity
import ( import (
"fmt"
"strings" "strings"
"npm/internal/model" "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 { return func(db *gorm.DB) *gorm.DB {
// todo for _, f := range filters {
/* // Lookup this filter field from the name map
if filters != nil { if _, ok := filterMap[f.Field]; ok {
filterMap := GetFilterMap(m) f.Field = filterMap[f.Field].Field
filterQuery, filterParams := GenerateSQLFromFilters(filters, filterMap, filterMapFunctions) }
whereStrings = []string{filterQuery}
params = append(params, filterParams...) // 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 return db
} }
} }
@ -60,3 +101,22 @@ func sortToOrderString(sorts []model.Sort) string {
} }
return strings.Join(strs, ", ") 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}
}

View File

@ -30,7 +30,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -21,7 +21,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -30,7 +30,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (entity.ListResponse,
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -41,7 +41,7 @@ func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ent
Direction: "ASC", 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 // Get count of items in this search
var totalRows int64 var totalRows int64

View File

@ -20,7 +20,7 @@ type Model struct {
Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"` Nickname string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"`
Email string `json:"email" gorm:"column:email" filter:"email,email"` Email string `json:"email" gorm:"column:email" filter:"email,email"`
IsDisabled bool `json:"is_disabled" gorm:"column:is_disabled" filter:"is_disabled,boolean"` 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 // Other
GravatarURL string `json:"gravatar_url" gorm:"-"` GravatarURL string `json:"gravatar_url" gorm:"-"`
// Expansions // Expansions

View File

@ -1,6 +1,9 @@
package util package util
import ( import (
"bytes"
"encoding/json"
"npm/internal/logger"
"regexp" "regexp"
"strings" "strings"
"unicode" "unicode"
@ -22,3 +25,16 @@ func CleanupWhitespace(s string) string {
return result 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()
}