mirror of
https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git
synced 2025-03-14 09:38:15 -04:00
Add entity filters back in for api
This commit is contained in:
parent
1ae247b2a6
commit
4b39ef0eba
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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())).
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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, ¶ms)))
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
return columns
|
merged[key] = value
|
||||||
}
|
}
|
||||||
|
return merged
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -2,52 +2,80 @@ 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 != "-" {
|
||||||
// split out tag value "field,filtreType"
|
schemas = append(schemas, getFilterTagSchema(filterTag))
|
||||||
// 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]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// newFilterSchema is the main method to specify a new Filter Schema for use in Middleware
|
||||||
|
@ -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
|
||||||
|
@ -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...)
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user