mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 15:23:34 +00:00 
			
		
		
		
	Refactor some reflection
This commit is contained in:
		| @@ -9,7 +9,6 @@ import ( | ||||
|  | ||||
| 	c "npm/internal/api/context" | ||||
| 	h "npm/internal/api/http" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/tags" | ||||
| 	"npm/internal/util" | ||||
| @@ -23,7 +22,7 @@ import ( | ||||
| // After we have determined what the Filters are to be, they are saved on the Context | ||||
| // to be used later in other endpoints. | ||||
| func ListQuery(obj interface{}) func(http.Handler) http.Handler { | ||||
| 	schemaData := entity.GetFilterSchema(obj, true) | ||||
| 	schemaData := tags.GetFilterSchema(obj) | ||||
| 	filterMap := tags.GetFilterMap(obj) | ||||
|  | ||||
| 	return func(next http.Handler) http.Handler { | ||||
|   | ||||
| @@ -2,8 +2,8 @@ package accesslist | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/user" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/types" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| @@ -11,7 +11,7 @@ import ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID uint        `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Name   string      `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	Meta   types.JSONB `json:"meta" gorm:"column:meta"` | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package auth | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| @@ -15,7 +15,7 @@ const ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID uint   `json:"user_id" gorm:"column:user_id"` | ||||
| 	Type   string `json:"type" gorm:"column:type;default:password"` | ||||
| 	Secret string `json:"secret,omitempty" gorm:"column:secret"` | ||||
|   | ||||
| @@ -10,11 +10,11 @@ import ( | ||||
| 	"npm/internal/acme" | ||||
| 	"npm/internal/config" | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/certificateauthority" | ||||
| 	"npm/internal/entity/dnsprovider" | ||||
| 	"npm/internal/entity/user" | ||||
| 	"npm/internal/logger" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/serverevents" | ||||
| 	"npm/internal/types" | ||||
| 	"npm/internal/util" | ||||
| @@ -44,7 +44,7 @@ const ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID                 uint                 `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Type                   string               `json:"type" gorm:"column:type" filter:"type,string"` | ||||
| 	CertificateAuthorityID types.NullableDBUint `json:"certificate_authority_id" gorm:"column:certificate_authority_id" filter:"certificate_authority_id,integer"` | ||||
|   | ||||
| @@ -5,15 +5,15 @@ import ( | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/errors" | ||||
| 	"npm/internal/model" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| ) | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	Name                string `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	AcmeshServer        string `json:"acmesh_server" gorm:"column:acmesh_server" filter:"acmesh_server,string"` | ||||
| 	CABundle            string `json:"ca_bundle" gorm:"column:ca_bundle" filter:"ca_bundle,string"` | ||||
|   | ||||
| @@ -5,8 +5,8 @@ import ( | ||||
|  | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/dnsproviders" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/logger" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/types" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| @@ -14,7 +14,7 @@ import ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID     uint        `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Name       string      `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	AcmeshName string      `json:"acmesh_name" gorm:"column:acmesh_name" filter:"acmesh_name,string"` | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
| func GetFilterMap(m interface{}, includeBaseEntity bool) map[string]model.FilterMapValue { | ||||
| 	filterMap := tags.GetFilterMap(m) | ||||
| 	if includeBaseEntity { | ||||
| 		return mergeFilterMaps(tags.GetFilterMap(ModelBase{}), filterMap) | ||||
| 		return mergeFilterMaps(tags.GetFilterMap(model.ModelBase{}), filterMap) | ||||
| 	} | ||||
|  | ||||
| 	return filterMap | ||||
|   | ||||
| @@ -1,252 +0,0 @@ | ||||
| package entity | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"npm/internal/logger" | ||||
| 	"npm/internal/util" | ||||
|  | ||||
| 	"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{}, 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 != "-" { | ||||
| 			schemas = append(schemas, getFilterTagSchema(filterTag)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| func newFilterSchema(fieldSchemas []string) string { | ||||
| 	return fmt.Sprintf(baseFilterSchema, strings.Join(fieldSchemas, ", ")) | ||||
| } | ||||
|  | ||||
| // boolFieldSchema returns the Field Schema for a Boolean accepted value field | ||||
| func boolFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					%s, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": %s | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, boolModifiers, filterBool, filterBool) | ||||
| } | ||||
|  | ||||
| // intFieldSchema returns the Field Schema for a Integer accepted value field | ||||
| func intFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "^[0-9]+$" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "^[0-9]+$" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, allModifiers) | ||||
| } | ||||
|  | ||||
| // stringFieldSchema returns the Field Schema for a String accepted value field | ||||
| func stringFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					%s, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": %s | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, stringModifiers, filterString, filterString) | ||||
| } | ||||
|  | ||||
| // regexFieldSchema  returns the Field Schema for a String accepted value field matching a Regex | ||||
| func regexFieldSchema(fieldName string, regex string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "%s" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "%s" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, stringModifiers, regex, regex) | ||||
| } | ||||
|  | ||||
| // dateFieldSchema returns the Field Schema for a String accepted value field matching a Date format | ||||
| func dateFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, allModifiers) | ||||
| } | ||||
|  | ||||
| const allModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not|contains|starts|ends|in|notin|min|max|greater|less)$" | ||||
| }` | ||||
|  | ||||
| const boolModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not)$" | ||||
| }` | ||||
|  | ||||
| const stringModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not|contains|starts|ends|in|notin)$" | ||||
| }` | ||||
|  | ||||
| const filterBool = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(TRUE|true|t|yes|y|on|1|FALSE|f|false|n|no|off|0)$" | ||||
| }` | ||||
|  | ||||
| const filterString = `{ | ||||
| 	"type": "string", | ||||
| 	"minLength": 1 | ||||
| }` | ||||
|  | ||||
| const baseFilterSchema = `{ | ||||
| 	"type": "array", | ||||
| 	"items": { | ||||
| 		"oneOf": [ | ||||
| 			%s | ||||
| 		] | ||||
| 	} | ||||
| }` | ||||
| @@ -3,11 +3,11 @@ package host | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/certificate" | ||||
| 	"npm/internal/entity/nginxtemplate" | ||||
| 	"npm/internal/entity/upstream" | ||||
| 	"npm/internal/entity/user" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/status" | ||||
| 	"npm/internal/types" | ||||
| 	"npm/internal/util" | ||||
| @@ -26,7 +26,7 @@ const ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID                uint                 `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Type                  string               `json:"type" gorm:"column:type" filter:"type,string"` | ||||
| 	NginxTemplateID       uint                 `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` | ||||
|   | ||||
| @@ -2,14 +2,14 @@ package nginxtemplate | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| ) | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID   uint   `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Name     string `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	Type     string `json:"type" gorm:"column:type" filter:"type,string"` | ||||
|   | ||||
| @@ -4,14 +4,14 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
|  | ||||
| 	"gorm.io/datatypes" | ||||
| ) | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	Name        string         `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	Description string         `json:"description" gorm:"column:description" filter:"description,string"` | ||||
| 	Value       datatypes.JSON `json:"value" gorm:"column:value"` | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package stream | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/types" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	ExpiresOn   types.DBDate `json:"expires_on" gorm:"column:expires_on" filter:"expires_on,integer"` | ||||
| 	UserID      uint         `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Provider    string       `json:"provider" gorm:"column:provider" filter:"provider,string"` | ||||
|   | ||||
| @@ -4,10 +4,10 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/nginxtemplate" | ||||
| 	"npm/internal/entity/upstreamserver" | ||||
| 	"npm/internal/entity/user" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/status" | ||||
| 	"npm/internal/util" | ||||
|  | ||||
| @@ -17,7 +17,7 @@ import ( | ||||
| // Model is the model | ||||
| // See: http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UserID            uint   `json:"user_id" gorm:"column:user_id" filter:"user_id,integer"` | ||||
| 	Name              string `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	NginxTemplateID   uint   `json:"nginx_template_id" gorm:"column:nginx_template_id" filter:"nginx_template_id,integer"` | ||||
|   | ||||
| @@ -2,12 +2,12 @@ package upstreamserver | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
| ) | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	UpstreamID  uint   `json:"upstream_id" gorm:"column:upstream_id" filter:"upstream_id,integer"` | ||||
| 	Server      string `json:"server" gorm:"column:server" filter:"server,string"` | ||||
| 	Weight      int    `json:"weight" gorm:"column:weight" filter:"weight,integer"` | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/auth" | ||||
| 	"npm/internal/errors" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/util" | ||||
|  | ||||
| 	"github.com/drexedam/gravatar" | ||||
| @@ -15,7 +16,7 @@ import ( | ||||
|  | ||||
| // Model is the model | ||||
| type Model struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	Name       string `json:"name" gorm:"column:name" filter:"name,string"` | ||||
| 	Nickname   string `json:"nickname" gorm:"column:nickname" filter:"nickname,string"` | ||||
| 	Email      string `json:"email" gorm:"column:email" filter:"email,email"` | ||||
|   | ||||
| @@ -2,14 +2,14 @@ package jwt | ||||
|  | ||||
| import ( | ||||
| 	"npm/internal/database" | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/model" | ||||
| ) | ||||
|  | ||||
| var currentKeys KeysModel | ||||
|  | ||||
| // KeysModel is the model | ||||
| type KeysModel struct { | ||||
| 	entity.ModelBase | ||||
| 	model.ModelBase | ||||
| 	PublicKey  string `gorm:"column:public_key"` | ||||
| 	PrivateKey string `gorm:"column:private_key"` | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,8 @@ type Filter struct { | ||||
|  | ||||
| // FilterMapValue ... | ||||
| type FilterMapValue struct { | ||||
| 	Type  string | ||||
| 	Field string | ||||
| 	Type   string | ||||
| 	Field  string | ||||
| 	Schema string | ||||
| 	Model  string | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package entity | ||||
| package model | ||||
| 
 | ||||
| import ( | ||||
| 	"gorm.io/plugin/soft_delete" | ||||
| @@ -3,9 +3,9 @@ package nginx | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"npm/internal/entity" | ||||
| 	"npm/internal/entity/certificate" | ||||
| 	"npm/internal/entity/host" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/types" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @@ -47,7 +47,7 @@ server { | ||||
| 				IsDisabled: false, | ||||
| 			}, | ||||
| 			cert: certificate.Model{ | ||||
| 				ModelBase: entity.ModelBase{ | ||||
| 				ModelBase: model.ModelBase{ | ||||
| 					ID: 77, | ||||
| 				}, | ||||
| 				Status:                 certificate.StatusProvided, | ||||
| @@ -65,7 +65,7 @@ server { | ||||
| 				IsDisabled: false, | ||||
| 			}, | ||||
| 			cert: certificate.Model{ | ||||
| 				ModelBase: entity.ModelBase{ | ||||
| 				ModelBase: model.ModelBase{ | ||||
| 					ID: 66, | ||||
| 				}, | ||||
| 				Status: certificate.StatusProvided, | ||||
|   | ||||
| @@ -1,11 +1,16 @@ | ||||
| package tags | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	"npm/internal/logger" | ||||
| 	"npm/internal/model" | ||||
| 	"npm/internal/util" | ||||
|  | ||||
| 	"github.com/rotisserie/eris" | ||||
| ) | ||||
|  | ||||
| func GetFilterMap(m interface{}) map[string]model.FilterMapValue { | ||||
| @@ -16,10 +21,21 @@ func GetFilterMap(m interface{}) map[string]model.FilterMapValue { | ||||
|  | ||||
| 	var filterMap = make(map[string]model.FilterMapValue) | ||||
|  | ||||
| 	// If this is an entity model (and it probably is) | ||||
| 	// then include the base model as well | ||||
| 	if strings.Contains(name, ".Model") && !strings.Contains(name, "ModelBase") { | ||||
| 		filterMap = GetFilterMap(model.ModelBase{}) | ||||
| 	} | ||||
|  | ||||
| 	// TypeOf returns the reflection Type that represents the dynamic type of variable. | ||||
| 	// If variable is a nil interface value, TypeOf returns nil. | ||||
| 	t := reflect.TypeOf(m) | ||||
|  | ||||
| 	if t.Kind() != reflect.Struct { | ||||
| 		logger.Error("GetFilterMapError", eris.Errorf("%v type can't have attributes inspected", t.Kind())) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Iterate over all available fields and read the tag value | ||||
| 	for i := 0; i < t.NumField(); i++ { | ||||
| 		// Get the field, returns https://golang.org/pkg/reflect/#StructField | ||||
| @@ -28,26 +44,247 @@ func GetFilterMap(m interface{}) map[string]model.FilterMapValue { | ||||
| 		// Get the field tag value | ||||
| 		filterTag := field.Tag.Get("filter") | ||||
| 		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 | ||||
| 		f := model.FilterMapValue{ | ||||
| 			Model: name, | ||||
| 		} | ||||
|  | ||||
| 		// Filter -> Schema mapping | ||||
| 		if filterTag != "" && filterTag != "-" { | ||||
| 			f.Schema = getFilterTagSchema(filterTag) | ||||
| 			parts := strings.Split(filterTag, ",") | ||||
| 			if len(parts) > 1 { | ||||
| 				filterMap[parts[0]] = model.FilterMapValue{ | ||||
| 					Type:  parts[1], | ||||
| 					Field: dbField, | ||||
|  | ||||
| 			// Filter -> DB Field mapping | ||||
| 			if dbTag != "" && dbTag != "-" { | ||||
| 				// db can have many parts, we need to pull out the "column:value" part | ||||
| 				f.Field = field.Name | ||||
| 				r := regexp.MustCompile(`(?:^|;)column:([^;|$]+)(?:$|;)`) | ||||
| 				if matches := r.FindStringSubmatch(dbTag); len(matches) > 1 { | ||||
| 					f.Field = 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 | ||||
| 				if len(parts) > 1 { | ||||
| 					f.Type = parts[1] | ||||
| 				} | ||||
| 			} | ||||
| 			filterMap[parts[0]] = f | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	setCache(name, filterMap) | ||||
| 	return filterMap | ||||
| } | ||||
|  | ||||
| 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]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	filterMap := GetFilterMap(m) | ||||
| 	schemas := make([]string, 0) | ||||
|  | ||||
| 	for _, f := range filterMap { | ||||
| 		schemas = append(schemas, f.Schema) | ||||
| 	} | ||||
|  | ||||
| 	str := fmt.Sprintf(baseFilterSchema, strings.Join(schemas, ", ")) | ||||
| 	return util.PrettyPrintJSON(str) | ||||
| } | ||||
|  | ||||
| // boolFieldSchema returns the Field Schema for a Boolean accepted value field | ||||
| func boolFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					%s, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": %s | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, boolModifiers, filterBool, filterBool) | ||||
| } | ||||
|  | ||||
| // intFieldSchema returns the Field Schema for a Integer accepted value field | ||||
| func intFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "^[0-9]+$" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "^[0-9]+$" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, allModifiers) | ||||
| } | ||||
|  | ||||
| // stringFieldSchema returns the Field Schema for a String accepted value field | ||||
| func stringFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					%s, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": %s | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, stringModifiers, filterString, filterString) | ||||
| } | ||||
|  | ||||
| // regexFieldSchema  returns the Field Schema for a String accepted value field matching a Regex | ||||
| func regexFieldSchema(fieldName string, regex string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "%s" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "%s" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, stringModifiers, regex, regex) | ||||
| } | ||||
|  | ||||
| // dateFieldSchema returns the Field Schema for a String accepted value field matching a Date format | ||||
| func dateFieldSchema(fieldName string) string { | ||||
| 	return fmt.Sprintf(`{ | ||||
| 		"type": "object", | ||||
| 		"properties": { | ||||
| 			"field": { | ||||
| 				"type": "string", | ||||
| 				"pattern": "^%s$" | ||||
| 			}, | ||||
| 			"modifier": %s, | ||||
| 			"value": { | ||||
| 				"oneOf": [ | ||||
| 					{ | ||||
| 						"type": "string", | ||||
| 						"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" | ||||
| 					}, | ||||
| 					{ | ||||
| 						"type": "array", | ||||
| 						"items": { | ||||
| 							"type": "string", | ||||
| 							"pattern": "^([12]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01]))$" | ||||
| 						} | ||||
| 					} | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	}`, fieldName, allModifiers) | ||||
| } | ||||
|  | ||||
| const allModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not|contains|starts|ends|in|notin|min|max|greater|less)$" | ||||
| }` | ||||
|  | ||||
| const boolModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not)$" | ||||
| }` | ||||
|  | ||||
| const stringModifiers = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(equals|not|contains|starts|ends|in|notin)$" | ||||
| }` | ||||
|  | ||||
| const filterBool = `{ | ||||
| 	"type": "string", | ||||
| 	"pattern": "^(TRUE|true|t|yes|y|on|1|FALSE|f|false|n|no|off|0)$" | ||||
| }` | ||||
|  | ||||
| const filterString = `{ | ||||
| 	"type": "string", | ||||
| 	"minLength": 1 | ||||
| }` | ||||
|  | ||||
| const baseFilterSchema = `{ | ||||
| 	"type": "array", | ||||
| 	"items": { | ||||
| 		"oneOf": [ | ||||
| 			%s | ||||
| 		] | ||||
| 	} | ||||
| }` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user