diff --git a/dbs/dbs.go b/dbs/dbs.go index ff1bd42..29d3163 100644 --- a/dbs/dbs.go +++ b/dbs/dbs.go @@ -2,6 +2,7 @@ package dbs import ( "fmt" + "reflect" "runtime/debug" "strings" @@ -148,6 +149,137 @@ type Filter struct { Value interface{} `json:"value,omitempty"` } +// FiltersFromFlatMap builds a *Filters from a map[string]interface{} whose structure +// mirrors the JSON form of Filters: +// +// { +// "and": { "name": [{"operator":"like","value":"foo"}] }, +// "or": { "source": [{"operator":"equal","value":"bar"}] } +// } +// +// Keys inside "and"/"or" are json tag names; the function resolves each to its +// full dotted BSON path using the target struct. Unknown keys are kept as-is. +func FiltersFromFlatMap(flatMap map[string]interface{}, target interface{}) *Filters { + filters := &Filters{ + And: make(map[string][]Filter), + Or: make(map[string][]Filter), + } + paths := jsonToBsonPaths(reflect.TypeOf(target), "") + resolve := func(jsonKey string) string { + if p, ok := paths[jsonKey]; ok { + return p + } + return jsonKey + } + parseFilters := func(raw interface{}) map[string][]Filter { + out := make(map[string][]Filter) + m, ok := raw.(map[string]interface{}) + if !ok { + return out + } + for jsonKey, val := range m { + bsonKey := resolve(jsonKey) + items, ok := val.([]interface{}) + if !ok { + continue + } + for _, item := range items { + entry, ok := item.(map[string]interface{}) + if !ok { + continue + } + f := Filter{} + if op, ok := entry["operator"].(string); ok { + f.Operator = op + } + if v, ok := entry["value"]; ok { + f.Value = v + } + out[bsonKey] = append(out[bsonKey], f) + } + } + return out + } + if and, ok := flatMap["and"]; ok { + filters.And = parseFilters(and) + } + if or, ok := flatMap["or"]; ok { + filters.Or = parseFilters(or) + } + return filters +} + +// jsonToBsonPaths recursively walks a struct type and returns a map of +// json_name → dotted_bson_path for every field reachable from that type. +// +// Anonymous embedded fields without any tag follow the BSON convention of this +// codebase: they are stored as a nested sub-document whose key is the lowercased +// struct type name (e.g. utils.AbstractObject → "abstractobject"). +func jsonToBsonPaths(t reflect.Type, prefix string) map[string]string { + for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice { + t = t.Elem() + } + result := make(map[string]string) + if t.Kind() != reflect.Struct { + return result + } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + jsonTag := field.Tag.Get("json") + bsonTag := field.Tag.Get("bson") + jsonName := strings.Split(jsonTag, ",")[0] + bsonName := strings.Split(bsonTag, ",")[0] + + // Anonymous embedded struct with no tags: use lowercase type name as BSON prefix. + if field.Anonymous && jsonName == "" && bsonName == "" { + ft := field.Type + for ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if ft.Kind() == reflect.Struct { + embedPrefix := strings.ToLower(ft.Name()) + if prefix != "" { + embedPrefix = prefix + "." + embedPrefix + } + for k, v := range jsonToBsonPaths(ft, embedPrefix) { + if _, exists := result[k]; !exists { + result[k] = v + } + } + } + continue + } + + if jsonName == "" || jsonName == "-" { + continue + } + if bsonName == "" || bsonName == "-" { + bsonName = jsonName + } + + fullPath := bsonName + if prefix != "" { + fullPath = prefix + "." + bsonName + } + + result[jsonName] = fullPath + + ft := field.Type + for ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice { + ft = ft.Elem() + } + if ft.Kind() == reflect.Struct { + for k, v := range jsonToBsonPaths(ft, fullPath) { + if _, exists := result[k]; !exists { + result[k] = v + } + } + } + } + return result +} + type Input = map[string]interface{} func InputToBson(i Input, isUpdate bool) bson.D { diff --git a/entrypoint.go b/entrypoint.go index d6322e3..a664e09 100644 --- a/entrypoint.go +++ b/entrypoint.go @@ -67,6 +67,10 @@ const ( ALLOWED_IMAGE = tools.ALLOWED_IMAGE ) +func FiltersFromFlatMap(flatMap map[string]interface{}, target interface{}) *dbs.Filters { + return dbs.FiltersFromFlatMap(flatMap, target) +} + func GetMySelf() (*peer.Peer, error) { pp, err := utils.GetMySelf((&peer.Peer{}).GetAccessor(&tools.APIRequest{Admin: true})) if pp == nil { diff --git a/models/resources/resource.go b/models/resources/resource.go index ed6bc61..638ae41 100755 --- a/models/resources/resource.go +++ b/models/resources/resource.go @@ -81,10 +81,6 @@ func (r *AbstractResource) CanUpdate(set utils.DBObject) (bool, utils.DBObject) return r.IsDraft, set } -func (r *AbstractResource) CanDelete() bool { - return r.IsDraft // only draft bookings can be deleted -} - type AbstractInstanciatedResource[T ResourceInstanceITF] struct { AbstractResource // AbstractResource contains the basic fields of an object (id, name) diff --git a/models/utils/common.go b/models/utils/common.go index a31a6f6..7ac6acd 100755 --- a/models/utils/common.go +++ b/models/utils/common.go @@ -78,9 +78,6 @@ func GenericDeleteOne(id string, a Accessor) (DBObject, int, error) { if !res.CanDelete() { return nil, 403, errors.New("you are not allowed to delete :" + a.GetType().String()) } - if err != nil { - return nil, code, err - } if a.ShouldVerifyAuth() && !res.VerifyAuth("delete", a.GetRequest()) { return nil, 403, errors.New("you are not allowed to access " + a.GetType().String()) } diff --git a/models/workflow/graph/item.go b/models/workflow/graph/item.go index 5e92ef6..d84600a 100644 --- a/models/workflow/graph/item.go +++ b/models/workflow/graph/item.go @@ -25,6 +25,8 @@ func (g *GraphItem) GetResource() (tools.DataType, resources.ResourceInterface) return tools.PROCESSING_RESOURCE, g.Processing } else if g.Storage != nil { return tools.STORAGE_RESOURCE, g.Storage + } else if g.NativeTool != nil { + return tools.NATIVE_TOOL, g.NativeTool } return tools.INVALID, nil }