From e8804dab067b2c167e12589970a627ab0ecdc866 Mon Sep 17 00:00:00 2001 From: devhindo Date: Tue, 31 Dec 2024 12:38:22 +0200 Subject: [PATCH] add initial implementation of mesh model types and filters Signed-off-by: devhindo --- models/meshmodel/core/types/types.go | 15 ++ models/meshmodel/core/v1alpha1/category.go | 82 +++++++ models/meshmodel/core/v1alpha1/component.go | 213 ++++++++++++++++++ models/meshmodel/core/v1alpha1/host.go | 37 +++ models/meshmodel/core/v1alpha1/models.go | 113 ++++++++++ models/meshmodel/core/v1alpha1/policy.go | 121 ++++++++++ .../meshmodel/core/v1alpha1/relationship.go | 164 ++++++++++++++ 7 files changed, 745 insertions(+) create mode 100644 models/meshmodel/core/types/types.go create mode 100644 models/meshmodel/core/v1alpha1/category.go create mode 100644 models/meshmodel/core/v1alpha1/component.go create mode 100644 models/meshmodel/core/v1alpha1/host.go create mode 100644 models/meshmodel/core/v1alpha1/models.go create mode 100644 models/meshmodel/core/v1alpha1/policy.go create mode 100644 models/meshmodel/core/v1alpha1/relationship.go diff --git a/models/meshmodel/core/types/types.go b/models/meshmodel/core/types/types.go new file mode 100644 index 00000000..92de25c5 --- /dev/null +++ b/models/meshmodel/core/types/types.go @@ -0,0 +1,15 @@ +package types + +type CapabilityType string + +const ( + ComponentDefinition CapabilityType = "component" + PolicyDefinition CapabilityType = "policy" + RelationshipDefinition CapabilityType = "relationship" + Model CapabilityType = "model" +) + +// Each entity will have it's own Filter implementation via which it exposes the nobs and dials to fetch entities +type Filter interface { + Create(map[string]interface{}) +} \ No newline at end of file diff --git a/models/meshmodel/core/v1alpha1/category.go b/models/meshmodel/core/v1alpha1/category.go new file mode 100644 index 00000000..8450f98a --- /dev/null +++ b/models/meshmodel/core/v1alpha1/category.go @@ -0,0 +1,82 @@ +package v1alpha1 + +import ( + "encoding/json" + "sync" + + "github.com/google/uuid" + "github.com/layer5io/meshkit/database" + "gorm.io/gorm" +) + +var categoryCreationLock sync.Mutex //Each model will perform a check and if the category already doesn't exist, it will create a category. This lock will make sure that there are no race conditions. + +// swagger:response Category +type Category struct { + ID uuid.UUID `json:"-" yaml:"-"` + Name string `json:"name"` + Metadata map[string]interface{} `json:"metadata" yaml:"metadata"` +} +type CategoryDB struct { + ID uuid.UUID `json:"-"` + Name string `json:"categoryName" gorm:"categoryName"` + Metadata []byte `json:"categoryMetadata" gorm:"categoryMetadata"` +} +type CategoryFilter struct { + Name string + OrderOn string + Greedy bool + Sort string //asc or desc. Default behavior is asc + Limit int //If 0 or unspecified then all records are returned and limit is not used + Offset int +} + +const DefaultCategory = "Miscellaneous" + +// Create the filter from map[string]interface{} +func (cf *CategoryFilter) Create(m map[string]interface{}) { + if m == nil { + return + } + cf.Name = m["name"].(string) +} +func CreateCategory(db *database.Handler, cat Category) (uuid.UUID, error) { + if cat.Name == "" { + cat.Name = DefaultCategory + } + byt, err := json.Marshal(cat) + if err != nil { + return uuid.UUID{}, err + } + catID := uuid.NewSHA1(uuid.UUID{}, byt) + var category CategoryDB + categoryCreationLock.Lock() + defer categoryCreationLock.Unlock() + err = db.First(&category, "id = ?", catID).Error + if err != nil && err != gorm.ErrRecordNotFound { + return uuid.UUID{}, err + } + if err == gorm.ErrRecordNotFound { //The category is already not present and needs to be inserted + cat.ID = catID + catdb := cat.GetCategoryDB(db) + err = db.Create(&catdb).Error + if err != nil { + return uuid.UUID{}, err + } + return catdb.ID, nil + } + return category.ID, nil +} + +func (cdb *CategoryDB) GetCategory(db *database.Handler) (cat Category) { + cat.ID = cdb.ID + cat.Name = cdb.Name + _ = json.Unmarshal(cdb.Metadata, &cat.Metadata) + return +} +func (c *Category) GetCategoryDB(db *database.Handler) (catdb CategoryDB) { + catdb.ID = c.ID + catdb.Name = c.Name + catdb.Metadata, _ = json.Marshal(c.Metadata) + return +} diff --git a/models/meshmodel/core/v1alpha1/component.go b/models/meshmodel/core/v1alpha1/component.go new file mode 100644 index 00000000..f31bde5d --- /dev/null +++ b/models/meshmodel/core/v1alpha1/component.go @@ -0,0 +1,213 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/layer5io/meshkit/database" + "github.com/layer5io/meshkit/models/meshmodel/core/types" + "gorm.io/gorm/clause" +) + +type TypeMeta struct { + Kind string `json:"kind,omitempty" yaml:"kind"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion"` +} +type ComponentFormat string + +const ( + JSON ComponentFormat = "JSON" + YAML ComponentFormat = "YAML" + CUE ComponentFormat = "CUE" +) + +// swagger:response ComponentDefinition +// use NewComponent function for instantiating +type ComponentDefinition struct { + ID uuid.UUID `json:"-"` + TypeMeta + DisplayName string `json:"displayName" gorm:"displayName"` + Format ComponentFormat `json:"format" yaml:"format"` + HostName string `json:"hostname"` + HostID uuid.UUID `json:"hostID"` + DisplayHostName string `json:"displayhostname"` + Metadata map[string]interface{} `json:"metadata" yaml:"metadata"` + Model Model `json:"model"` + Schema string `json:"schema,omitempty" yaml:"schema"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} +type ComponentDefinitionDB struct { + ID uuid.UUID `json:"-"` + ModelID uuid.UUID `json:"-" gorm:"modelID"` + TypeMeta + DisplayName string `json:"displayName" gorm:"displayName"` + Format ComponentFormat `json:"format" yaml:"format"` + Metadata []byte `json:"metadata" yaml:"metadata"` + Schema string `json:"schema,omitempty" yaml:"schema"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} + +func (c ComponentDefinition) Type() types.CapabilityType { + return types.ComponentDefinition +} +func (c ComponentDefinition) GetID() uuid.UUID { + return c.ID +} +func emptySchemaCheck(schema string) (valid bool) { + if schema == "" { + return + } + m := make(map[string]interface{}) + _ = json.Unmarshal([]byte(schema), &m) + if m["properties"] == nil { + return + } + valid = true + return +} +func CreateComponent(db *database.Handler, c ComponentDefinition) (uuid.UUID, uuid.UUID, error) { + c.ID = uuid.New() + mid, err := CreateModel(db, c.Model) + if err != nil { + return uuid.UUID{}, uuid.UUID{}, err + } + + if !emptySchemaCheck(c.Schema) { + c.Metadata["hasInvalidSchema"] = true + } + cdb := c.GetComponentDefinitionDB() + cdb.ModelID = mid + err = db.Create(&cdb).Error + return c.ID, mid, err +} +func GetMeshModelComponents(db *database.Handler, f ComponentFilter) (c []ComponentDefinition, count int64, unique int) { + type componentDefinitionWithModel struct { + ComponentDefinitionDB + ModelDB + CategoryDB + } + + countUniqueComponents := func(components []componentDefinitionWithModel) int { + set := make(map[string]struct{}) + for _, model := range components { + key := model.ComponentDefinitionDB.Kind + "@" + model.APIVersion + "@" + model.ModelDB.Name + "@" + model.ModelDB.Version + if _, ok := set[key]; !ok { + set[key] = struct{}{} + } + } + return len(set) + } + + var componentDefinitionsWithModel []componentDefinitionWithModel + finder := db.Model(&ComponentDefinitionDB{}). + Select("component_definition_dbs.*, model_dbs.*,category_dbs.*"). + Joins("JOIN model_dbs ON component_definition_dbs.model_id = model_dbs.id"). + Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") // + if f.Greedy { + if f.Name != "" && f.DisplayName != "" { + finder = finder.Where("component_definition_dbs.kind LIKE ? OR display_name LIKE ?", "%"+f.Name+"%", f.DisplayName+"%") + } else if f.Name != "" { + finder = finder.Where("component_definition_dbs.kind LIKE ?", "%"+f.Name+"%") + } else if f.DisplayName != "" { + finder = finder.Where("component_definition_dbs.display_name LIKE ?", "%"+f.DisplayName+"%") + } + } else { + if f.Name != "" { + finder = finder.Where("component_definition_dbs.kind = ?", f.Name) + } + if f.DisplayName != "" { + finder = finder.Where("component_definition_dbs.display_name = ?", f.DisplayName) + } + } + if f.ModelName != "" && f.ModelName != "all" { + finder = finder.Where("model_dbs.name = ?", f.ModelName) + } + if f.APIVersion != "" { + finder = finder.Where("component_definition_dbs.api_version = ?", f.APIVersion) + } + if f.CategoryName != "" { + finder = finder.Where("category_dbs.name = ?", f.CategoryName) + } + if f.Version != "" { + finder = finder.Where("model_dbs.version = ?", f.Version) + } + if f.OrderOn != "" { + if f.Sort == "desc" { + finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: f.OrderOn}, Desc: true}) + } else { + finder = finder.Order(f.OrderOn) + } + } + + finder.Count(&count) + + finder = finder.Offset(f.Offset) + if f.Limit != 0 { + finder = finder.Limit(f.Limit) + } + err := finder. + Scan(&componentDefinitionsWithModel).Error + if err != nil { + fmt.Println(err.Error()) //for debugging + } + for _, cm := range componentDefinitionsWithModel { + if f.Trim { + cm.Schema = "" + } + c = append(c, cm.ComponentDefinitionDB.GetComponentDefinition(cm.ModelDB.GetModel(cm.CategoryDB.GetCategory(db)))) + } + + unique = countUniqueComponents(componentDefinitionsWithModel) + + return c, count, unique +} + +type ComponentFilter struct { + Name string + APIVersion string + Greedy bool //when set to true - instead of an exact match, name will be prefix matched + Trim bool //when set to true - the schema is not returned + DisplayName string + ModelName string + CategoryName string + Version string + Sort string //asc or desc. Default behavior is asc + OrderOn string + Limit int //If 0 or unspecified then all records are returned and limit is not used + Offset int +} + +// Create the filter from map[string]interface{} +func (cf *ComponentFilter) Create(m map[string]interface{}) { + if m == nil { + return + } + cf.Name = m["name"].(string) +} + +func (cmd *ComponentDefinitionDB) GetComponentDefinition(model Model) (c ComponentDefinition) { + c.ID = cmd.ID + c.TypeMeta = cmd.TypeMeta + c.Format = cmd.Format + c.DisplayName = cmd.DisplayName + if c.Metadata == nil { + c.Metadata = make(map[string]interface{}) + } + _ = json.Unmarshal(cmd.Metadata, &c.Metadata) + c.Schema = cmd.Schema + c.Model = model + return +} +func (c *ComponentDefinition) GetComponentDefinitionDB() (cmd ComponentDefinitionDB) { + cmd.ID = c.ID + cmd.TypeMeta = c.TypeMeta + cmd.Format = c.Format + cmd.Metadata, _ = json.Marshal(c.Metadata) + cmd.DisplayName = c.DisplayName + cmd.Schema = c.Schema + return +} diff --git a/models/meshmodel/core/v1alpha1/host.go b/models/meshmodel/core/v1alpha1/host.go new file mode 100644 index 00000000..fd3ece5c --- /dev/null +++ b/models/meshmodel/core/v1alpha1/host.go @@ -0,0 +1,37 @@ +package v1alpha1 + +import "github.com/google/uuid" + +type MeshModelHostsWithEntitySummary struct { + ID uuid.UUID `json:"id"` + Hostname string `json:"hostname"` + Port int `json:"port"` + Summary EntitySummary `json:"summary"` +} +type EntitySummary struct { + Models int64 `json:"models"` + Components int64 `json:"components"` + Relationships int64 `json:"relationships"` + Policies int64 `json:"policies"` +} +type MesheryHostSummaryDB struct { + HostID uuid.UUID `json:"-" gorm:"id"` + Hostname string `json:"-" gorm:"hostname"` + Port int `json:"-" gorm:"port"` + Models int64 `json:"-" gorm:"models"` + Components int64 `json:"-" gorm:"components"` + Relationships int64 `json:"-" gorm:"relationships"` + Policies int64 `json:"-" gorm:"policies"` +} + +type HostFilter struct { + Name string + Greedy bool //when set to true - instead of an exact match, name will be prefix matched + Trim bool //when set to true - the schema is not returned + DisplayName string + Version string + Sort string //asc or desc. Default behavior is asc + OrderOn string + Limit int //If 0 or unspecified then all records are returned and limit is not used + Offset int +} diff --git a/models/meshmodel/core/v1alpha1/models.go b/models/meshmodel/core/v1alpha1/models.go new file mode 100644 index 00000000..a8d53afc --- /dev/null +++ b/models/meshmodel/core/v1alpha1/models.go @@ -0,0 +1,113 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "sync" + + "github.com/google/uuid" + "github.com/layer5io/meshkit/database" + "github.com/layer5io/meshkit/models/meshmodel/core/types" + "gorm.io/gorm" +) + +var modelCreationLock sync.Mutex //Each component/relationship will perform a check and if the model already doesn't exist, it will create a model. This lock will make sure that there are no race conditions. +type ModelFilter struct { + Name string + Registrant string //name of the registrant for a given model + DisplayName string //If Name is already passed, avoid passing Display name unless greedy=true, else the filter will translate to an AND returning only the models where name and display name match exactly. Ignore, if this behavior is expected. + Greedy bool //when set to true - instead of an exact match, name will be prefix matched. Also an OR will be performed of name and display_name + Version string + Category string + OrderOn string + Sort string //asc or desc. Default behavior is asc + Limit int //If 0 or unspecified then all records are returned and limit is not used + Offset int +} + +// Create the filter from map[string]interface{} +func (cf *ModelFilter) Create(m map[string]interface{}) { + if m == nil { + return + } + cf.Name = m["name"].(string) +} + +// swagger:response Model +type Model struct { + ID uuid.UUID `json:"-" yaml:"-"` + Name string `json:"name"` + Version string `json:"version"` + DisplayName string `json:"displayName" gorm:"modelDisplayName"` + HostName string `json:"hostname"` + HostID uuid.UUID `json:"hostID"` + DisplayHostName string `json:"displayhostname"` + Category Category `json:"category"` + Metadata map[string]interface{} `json:"metadata" yaml:"modelMetadata"` +} +type ModelDB struct { + ID uuid.UUID `json:"-"` + CategoryID uuid.UUID `json:"-" gorm:"categoryID"` + Name string `json:"modelName" gorm:"modelName"` + Version string `json:"version"` + DisplayName string `json:"modelDisplayName" gorm:"modelDisplayName"` + SubCategory string `json:"subCategory" gorm:"subCategory"` + Metadata []byte `json:"modelMetadata" gorm:"modelMetadata"` +} + +func (m Model) Type() types.CapabilityType { + return types.Model +} +func (m Model) GetID() uuid.UUID { + return m.ID +} + +func CreateModel(db *database.Handler, cmodel Model) (uuid.UUID, error) { + byt, err := json.Marshal(cmodel) + if err != nil { + return uuid.UUID{}, err + } + modelID := uuid.NewSHA1(uuid.UUID{}, byt) + var model ModelDB + if cmodel.Name == "" { + return uuid.UUID{}, fmt.Errorf("empty or invalid model name passed") + } + modelCreationLock.Lock() + defer modelCreationLock.Unlock() + err = db.First(&model, "id = ?", modelID).Error + if err != nil && err != gorm.ErrRecordNotFound { + return uuid.UUID{}, err + } + if err == gorm.ErrRecordNotFound { //The model is already not present and needs to be inserted + id, err := CreateCategory(db, cmodel.Category) + if err != nil { + return uuid.UUID{}, err + } + cmodel.ID = modelID + mdb := cmodel.GetModelDB() + mdb.CategoryID = id + err = db.Create(&mdb).Error + if err != nil { + return uuid.UUID{}, err + } + return mdb.ID, nil + } + return model.ID, nil +} +func (cmd *ModelDB) GetModel(cat Category) (c Model) { + c.ID = cmd.ID + c.Category = cat + c.DisplayName = cmd.DisplayName + c.Name = cmd.Name + c.Version = cmd.Version + _ = json.Unmarshal(cmd.Metadata, &c.Metadata) + return +} +func (c *Model) GetModelDB() (cmd ModelDB) { + cmd.ID = c.ID + cmd.DisplayName = c.DisplayName + cmd.Name = c.Name + cmd.Version = c.Version + cmd.Metadata, _ = json.Marshal(c.Metadata) + return +} diff --git a/models/meshmodel/core/v1alpha1/policy.go b/models/meshmodel/core/v1alpha1/policy.go new file mode 100644 index 00000000..0791b705 --- /dev/null +++ b/models/meshmodel/core/v1alpha1/policy.go @@ -0,0 +1,121 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/layer5io/meshkit/database" + "github.com/layer5io/meshkit/models/meshmodel/core/types" +) + +type PolicyDefinition struct { + ID uuid.UUID `json:"-"` + TypeMeta + Model Model `json:"model"` + SubType string `json:"subType" yaml:"subType"` + Expression map[string]interface{} `json:"expression" yaml:"expression"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} + +type PolicyDefinitionDB struct { + ID uuid.UUID `json:"-"` + ModelID uuid.UUID `json:"-" gorm:"modelID"` + TypeMeta + SubType string `json:"subType" yaml:"subType"` + Expression []byte `json:"expression" yaml:"expression"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} + +type PolicyFilter struct { + Kind string + Greedy bool + SubType string + ModelName string + OrderOn string + Sort string + Limit int + Offset int +} + +func (pf *PolicyFilter) Create(m map[string]interface{}) { + if m == nil { + return + } +} + +func (p PolicyDefinition) GetID() uuid.UUID { + return p.ID +} + +func (p PolicyDefinition) Type() types.CapabilityType { + return types.PolicyDefinition +} + +func GetMeshModelPolicy(db *database.Handler, f PolicyFilter) (pl []PolicyDefinition) { + type componentDefinitionWithModel struct { + PolicyDefinitionDB + Model + } + var componentDefinitionsWithModel []componentDefinitionWithModel + finder := db.Model(&PolicyDefinitionDB{}). + Select("policy_definition_dbs.*, models_dbs.*"). + Joins("JOIN model_dbs ON model_dbs.id = policy_definition_dbs.model_id") + if f.Kind != "" { + finder = finder.Where("policy_definition_dbs.kind = ?", f.Kind) + } + if f.SubType != "" { + finder = finder.Where("policy_definition_dbs.sub_type = ?", f.SubType) + } + if f.ModelName != "" { + finder = finder.Where("model_dbs.name = ?", f.ModelName) + } + err := finder.Scan(&componentDefinitionsWithModel).Error + if err != nil { + fmt.Println(err.Error()) + } + for _, cm := range componentDefinitionsWithModel { + pl = append(pl, cm.PolicyDefinitionDB.GetPolicyDefinition(cm.Model)) + } + return pl +} + +func (pdb *PolicyDefinitionDB) GetPolicyDefinition(m Model) (p PolicyDefinition) { + p.ID = pdb.ID + p.TypeMeta = pdb.TypeMeta + p.Model = m + p.SubType = pdb.SubType + if p.Expression == nil { + p.Expression = make(map[string]interface{}) + } + _ = json.Unmarshal(pdb.Expression, &p.Expression) + + return +} + +func CreatePolicy(db *database.Handler, p PolicyDefinition) (uuid.UUID, uuid.UUID, error) { + p.ID = uuid.New() + mid, err := CreateModel(db, p.Model) + if err != nil { + return uuid.UUID{}, uuid.UUID{}, err + } + pdb := p.GetPolicyDefinitionDB() + pdb.ModelID = mid + err = db.Create(&pdb).Error + if err != nil { + return uuid.UUID{}, uuid.UUID{}, err + } + return pdb.ID, mid, nil +} + +func (p *PolicyDefinition) GetPolicyDefinitionDB() (pdb PolicyDefinitionDB) { + pdb.ID = p.ID + pdb.TypeMeta = p.TypeMeta + pdb.SubType = p.SubType + pdb.ModelID = p.Model.ID + pdb.Expression, _ = json.Marshal(p.Expression) + return +} diff --git a/models/meshmodel/core/v1alpha1/relationship.go b/models/meshmodel/core/v1alpha1/relationship.go new file mode 100644 index 00000000..747595ba --- /dev/null +++ b/models/meshmodel/core/v1alpha1/relationship.go @@ -0,0 +1,164 @@ +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/layer5io/meshkit/database" + "github.com/layer5io/meshkit/models/meshmodel/core/types" + "gorm.io/gorm/clause" +) + +// https://docs.google.com/drawings/d/1_qzQ_YxvCWPYrOBcdqGMlMwfbsZx96SBuIkbn8TfKhU/edit?pli=1 +// see RELATIONSHIPDEFINITIONS table in the diagram +// swagger:response RelationshipDefinition +// TODO: Add support for Model +type RelationshipDefinition struct { + ID uuid.UUID `json:"-"` + TypeMeta + Model Model `json:"model"` + HostName string `json:"hostname"` + HostID uuid.UUID `json:"hostID"` + DisplayHostName string `json:"displayhostname"` + Metadata map[string]interface{} `json:"metadata" yaml:"metadata"` + SubType string `json:"subType" yaml:"subType" gorm:"subType"` + Selectors map[string]interface{} `json:"selectors" yaml:"selectors"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} + +type RelationshipDefinitionDB struct { + ID uuid.UUID `json:"-"` + ModelID uuid.UUID `json:"-" gorm:"modelID"` + TypeMeta + Metadata []byte `json:"metadata" yaml:"metadata"` + SubType string `json:"subType" yaml:"subType"` + Selectors []byte `json:"selectors" yaml:"selectors"` + CreatedAt time.Time `json:"-"` + UpdatedAt time.Time `json:"-"` +} + +// For now, only filtering by Kind and SubType are allowed. +// In the future, we will add support to query using `selectors` (using CUE) +// TODO: Add support for Model +type RelationshipFilter struct { + Kind string + Greedy bool //when set to true - instead of an exact match, kind will be prefix matched + SubType string + Version string + ModelName string + OrderOn string + Sort string //asc or desc. Default behavior is asc + Limit int //If 0 or unspecified then all records are returned and limit is not used + Offset int +} + +// Create the filter from map[string]interface{} +func (rf *RelationshipFilter) Create(m map[string]interface{}) { + if m == nil { + return + } +} +func GetMeshModelRelationship(db *database.Handler, f RelationshipFilter) (r []RelationshipDefinition, count int64) { + type componentDefinitionWithModel struct { + RelationshipDefinitionDB + ModelDB + CategoryDB + } + var componentDefinitionsWithModel []componentDefinitionWithModel + finder := db.Model(&RelationshipDefinitionDB{}). + Select("relationship_definition_dbs.*, model_dbs.*"). + Joins("JOIN model_dbs ON relationship_definition_dbs.model_id = model_dbs.id"). // + Joins("JOIN category_dbs ON model_dbs.category_id = category_dbs.id") // + if f.Kind != "" { + if f.Greedy { + finder = finder.Where("relationship_definition_dbs.kind LIKE ?", "%"+f.Kind+"%") + } else { + finder = finder.Where("relationship_definition_dbs.kind = ?", f.Kind) + } + } + if f.SubType != "" { + finder = finder.Where("relationship_definition_dbs.sub_type = ?", f.SubType) + } + if f.ModelName != "" { + finder = finder.Where("model_dbs.name = ?", f.ModelName) + } + if f.Version != "" { + finder = finder.Where("model_dbs.version = ?", f.Version) + } + if f.OrderOn != "" { + if f.Sort == "desc" { + finder = finder.Order(clause.OrderByColumn{Column: clause.Column{Name: f.OrderOn}, Desc: true}) + } else { + finder = finder.Order(f.OrderOn) + } + } + + finder.Count(&count) + + finder = finder.Offset(f.Offset) + if f.Limit != 0 { + finder = finder.Limit(f.Limit) + } + err := finder. + Scan(&componentDefinitionsWithModel).Error + if err != nil { + fmt.Println(err.Error()) //for debugging + } + for _, cm := range componentDefinitionsWithModel { + r = append(r, cm.RelationshipDefinitionDB.GetRelationshipDefinition(cm.ModelDB.GetModel(cm.CategoryDB.GetCategory(db)))) + } + return r, count +} + +func (rdb *RelationshipDefinitionDB) GetRelationshipDefinition(m Model) (r RelationshipDefinition) { + r.ID = rdb.ID + r.TypeMeta = rdb.TypeMeta + if r.Metadata == nil { + r.Metadata = make(map[string]interface{}) + } + _ = json.Unmarshal(rdb.Metadata, &r.Metadata) + if r.Selectors == nil { + r.Selectors = make(map[string]interface{}) + } + _ = json.Unmarshal(rdb.Selectors, &r.Selectors) + r.SubType = rdb.SubType + r.Kind = rdb.Kind + r.Model = m + return +} + +func (r RelationshipDefinition) Type() types.CapabilityType { + return types.RelationshipDefinition +} +func (r RelationshipDefinition) GetID() uuid.UUID { + return r.ID +} + +func CreateRelationship(db *database.Handler, r RelationshipDefinition) (uuid.UUID, uuid.UUID, error) { + r.ID = uuid.New() + mid, err := CreateModel(db, r.Model) + if err != nil { + return uuid.UUID{}, uuid.UUID{}, err + } + rdb := r.GetRelationshipDefinitionDB() + rdb.ModelID = mid + err = db.Create(&rdb).Error + if err != nil { + return uuid.UUID{}, uuid.UUID{}, err + } + return r.ID, mid, err +} + +func (r *RelationshipDefinition) GetRelationshipDefinitionDB() (rdb RelationshipDefinitionDB) { + rdb.ID = r.ID + rdb.TypeMeta = r.TypeMeta + rdb.Metadata, _ = json.Marshal(r.Metadata) + rdb.Selectors, _ = json.Marshal(r.Selectors) + rdb.Kind = r.Kind + rdb.SubType = r.SubType + rdb.ModelID = r.Model.ID + return +}