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"
"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
}

View File

@ -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())).

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, &params)))
}
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
}

View File

@ -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

View File

@ -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

View File

@ -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...)
}

View File

@ -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"`
}

View File

@ -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

View File

@ -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}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}