Skip to content

Commit

Permalink
Feature api cli generic objects (#321)
Browse files Browse the repository at this point in the history
* feat(api): recursive get objects

* feat(cli): get and ls recursive

* feat(cli): show relative path when doing recursive ls

* fix(cli): order objects on get

* docs(cli): recursive ls and get

* feat(cli): get with filters and ls -r with filters

* feat(api): support ** (also directy children)

* feat(cli): add support for min and max depth in recursive ls/get

* feat(api): add support for min and max depth in wildcards

* fix(cli): be able to ls format without sort

* fix(cli): man are txt files

* fix(cli): lsdev

* docs(cli): more docs to ls, lsobj and get

* fix(cli,api): wildcards following linux standard

* fix(cli): not error when layer is empty

* fix(cli): relative path when ls -r is not path

* feat(api): create generic objects

* fix(cli): delete grep

* refactor(cli): avoid not neccesary error verification

* docs(cli): fix lint and typos

* refactor(cli): remove duplicated code for template, posXYZ and size

* refactor(cli): serialise vector

* refactor(cli): create attributes

* refactor(cli): apply template

* feat(cli): verify that template category is the correct one

* feat(cli): create generic object

* feat(api): add templates to generic

* feat(cli): add generics layers

* feat(cli): create generic templates

* docs(cli): create generic and generic template

* fix(cli): build

* Update wildcard_test.go

* fix(api) strict use of transactions

* fix(api) tests

* fix(api) minor fixes

* fix(api,cli) add shapes

* fix(api) tests

* fix(api,cli) tests

---------

Co-authored-by: Franco Liberali <[email protected]>
  • Loading branch information
helderbetiol and FrancoLiberali authored Jan 4, 2024
1 parent a138326 commit ae6733c
Show file tree
Hide file tree
Showing 47 changed files with 2,047 additions and 979 deletions.
20 changes: 10 additions & 10 deletions API/controllers/wildcard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import (
)

func init() {
integration.CreateSite("wildcard-site-1")
integration.CreateBuilding("wildcard-site-1", "wildcard-building-1")
integration.CreateBuilding("wildcard-site-1", "wildcard-building-2")
integration.CreateRoom("wildcard-site-1.wildcard-building-1", "wildcard-1")
integration.CreateRack("wildcard-site-1.wildcard-building-1.wildcard-1", "wildcard-1")
integration.CreateDevice("wildcard-site-1.wildcard-building-1.wildcard-1.wildcard-1", "wildcard-device-1")
integration.CreateRoom("wildcard-site-1.wildcard-building-2", "wildcard-1")
integration.CreateRack("wildcard-site-1.wildcard-building-2.wildcard-1", "wildcard-2")
integration.CreateSite("wildcard-site-2")
integration.CreateBuilding("wildcard-site-2", "wildcard-building-3")
integration.RequireCreateSite("wildcard-site-1")
integration.RequireCreateBuilding("wildcard-site-1", "wildcard-building-1")
integration.RequireCreateBuilding("wildcard-site-1", "wildcard-building-2")
integration.RequireCreateRoom("wildcard-site-1.wildcard-building-1", "wildcard-1")
integration.RequireCreateRack("wildcard-site-1.wildcard-building-1.wildcard-1", "wildcard-1")
integration.RequireCreateDevice("wildcard-site-1.wildcard-building-1.wildcard-1.wildcard-1", "wildcard-device-1")
integration.RequireCreateRoom("wildcard-site-1.wildcard-building-2", "wildcard-1")
integration.RequireCreateRack("wildcard-site-1.wildcard-building-2.wildcard-1", "wildcard-2")
integration.RequireCreateSite("wildcard-site-2")
integration.RequireCreateBuilding("wildcard-site-2", "wildcard-building-3")
}

func TestWildcardSomethingStarReturnsSites(t *testing.T) {
Expand Down
112 changes: 112 additions & 0 deletions API/models/create_object_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package models_test

import (
"p3/models"
"p3/test/integration"
"p3/test/unit"
u "p3/utils"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCreateRackWithoutAttributesReturnsError(t *testing.T) {
_, err := models.CreateEntity(
u.RACK,
map[string]any{
"category": "rack",
"description": []any{"rack"},
"domain": integration.TestDBName,
"name": "create-object-1",
"tags": []any{},
},
integration.ManagerUserRoles,
)
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "JSON body doesn't validate with the expected JSON schema", err.Message)
}

func TestCreateObjectWithDuplicatedNameReturnsError(t *testing.T) {
site := integration.RequireCreateSite("create-object-1")

_, err := integration.CreateSite(site["name"].(string))
assert.NotNil(t, err)
assert.Equal(t, u.ErrDuplicate, err.Type)
assert.Equal(t, "Error while creating site: Duplicates not allowed", err.Message)
}

func TestCreateCorridorWithSameNameAsRackReturnsError(t *testing.T) {
rack := integration.RequireCreateRack("", "create-object-2")

_, err := integration.CreateCorridor(rack["parentId"].(string), "create-object-2")
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Object name must be unique among corridors, racks and generic objects", err.Message)
}

func TestCreateRackWithSameNameAsCorridorReturnsError(t *testing.T) {
corridor := integration.RequireCreateCorridor("", "create-object-3")

_, err := integration.CreateRack(corridor["parentId"].(string), "create-object-3")
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Object name must be unique among corridors, racks and generic objects", err.Message)
}

func TestCreateGenericWithSameNameAsRackReturnsError(t *testing.T) {
rack := integration.RequireCreateRack("", "create-object-4")

_, err := integration.CreateGeneric(rack["parentId"].(string), "create-object-4")
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Object name must be unique among corridors, racks and generic objects", err.Message)
}

func TestCreateGenericWithSameNameAsCorridorReturnsError(t *testing.T) {
corridor := integration.RequireCreateCorridor("", "create-object-5")

_, err := integration.CreateGeneric(corridor["parentId"].(string), "create-object-5")
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Object name must be unique among corridors, racks and generic objects", err.Message)
}

func TestCreateGroupWithObjectThatNotExistsReturnsError(t *testing.T) {
room := integration.RequireCreateRoom("", "create-object-6-room")

_, err := integration.CreateGroup(room["id"].(string), "create-object-6", []string{"not-exists"})
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Some object(s) could not be found. Please check and try again", err.Message)
}

func TestCreateGroupWithCorridorsRacksAndGenericWorks(t *testing.T) {
room := integration.RequireCreateRoom("", "create-object-7-room")
rack := integration.RequireCreateRack(room["id"].(string), "create-object-7-rack")
corridor := integration.RequireCreateCorridor(room["id"].(string), "create-object-7-corridor")
generic := integration.RequireCreateGeneric(room["id"].(string), "create-object-7-generic")

group, err := integration.CreateGroup(
room["id"].(string),
"create-object-7",
[]string{rack["name"].(string), corridor["name"].(string), generic["name"].(string)},
)
assert.Nil(t, err)
unit.HasAttribute(t, group, "content", "create-object-7-rack,create-object-7-corridor,create-object-7-generic")
}

func TestCreateGenericWithParentNotRoomReturnsError(t *testing.T) {
rack := integration.RequireCreateRack("", "create-object-8-rack")

_, err := integration.CreateGeneric(rack["id"].(string), "create-object-8-generic")
assert.NotNil(t, err)
assert.ErrorContains(t, err, "ParentID should correspond to Existing Room ID")
}

func TestCreateGenericWithParentRoomWorks(t *testing.T) {
room := integration.RequireCreateRoom("", "create-object-9-room")

_, err := integration.CreateGeneric(room["id"].(string), "create-object-9-generic")
assert.Nil(t, err)
}
5 changes: 1 addition & 4 deletions API/models/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/elliotchance/pie/v2"
"github.com/vincent-petithory/dataurl"
"go.mongodb.org/mongo-driver/mongo"
)

const imageMaxSizeInMegaBytes = 16
Expand Down Expand Up @@ -49,7 +48,5 @@ func createImageFromDataURI(ctx context.Context, dataURI string) (any, *u.Error)

// Returns image with "id" from database
func GetImage(id string) (*u.Image, *u.Error) {
return WithTransaction(func(ctx mongo.SessionContext) (*u.Image, error) {
return repository.GetImage(ctx, id)
})
return repository.GetImage(id)
}
134 changes: 64 additions & 70 deletions API/models/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ func updateOldObjWithPatch(old map[string]interface{}, patch map[string]interfac
// Entity handlers

func CreateEntity(entity int, t map[string]interface{}, userRoles map[string]Role) (map[string]interface{}, *u.Error) {
tags, tagsPresent := getTags(t)
if tagsPresent {
err := verifyTagList(tags)
if err != nil {
return nil, err
}
}

if err := prepareCreateEntity(entity, t, userRoles); err != nil {
return nil, err
}
Expand All @@ -120,14 +128,6 @@ func CreateEntity(entity int, t map[string]interface{}, userRoles map[string]Rol
}
}

tags, tagsPresent := getTags(t)
if tagsPresent {
err := verifyTagList(ctx, tags)
if err != nil {
return nil, err
}
}

entStr := u.EntityToString(entity)

_, err := repository.CreateObject(ctx, entStr, t)
Expand Down Expand Up @@ -181,15 +181,12 @@ func GetHierarchyObjectById(hierarchyName string, filters u.RequestFilters, user
}

func GetObject(req bson.M, entityStr string, filters u.RequestFilters, userRoles map[string]Role) (map[string]interface{}, *u.Error) {
objectAny, err := WithTransaction(func(ctx mongo.SessionContext) (any, error) {
return repository.GetObject(ctx, req, entityStr, filters)
})
object, err := repository.GetObject(req, entityStr, filters)

if err != nil {
return nil, err
}

object := objectAny.(map[string]any)

//Remove _id
object = fixID(object)

Expand Down Expand Up @@ -613,12 +610,9 @@ func DeleteHierarchicalObject(entity string, id string, userRoles map[string]Rol

func DeleteNonHierarchicalObject(entity, slug string) *u.Error {
req := bson.M{"slug": slug}

_, err := WithTransaction(func(ctx mongo.SessionContext) (any, error) {
return nil, repository.DeleteObject(ctx, entity, req)
})

return err
ctx, cancel := u.Connect()
defer cancel()
return repository.DeleteObject(ctx, entity, req)
}

func prepareUpdateObject(ctx mongo.SessionContext, entity int, id string, updateData, oldObject map[string]any, userRoles map[string]Role) *u.Error {
Expand All @@ -630,7 +624,7 @@ func prepareUpdateObject(ctx mongo.SessionContext, entity int, id string, update
}

// tag list edition support
err := addAndRemoveFromTags(ctx, entity, id, updateData)
err := addAndRemoveFromTags(entity, id, updateData)
if err != nil {
return err
}
Expand Down Expand Up @@ -674,66 +668,66 @@ func UpdateObject(entityStr string, id string, updateData map[string]interface{}
idFilter = bson.M{"id": id}
}

result, err := WithTransaction(func(ctx mongo.SessionContext) (interface{}, error) {
//Update timestamp requires first obj retrieval
//there isn't any way for mongoDB to make a field
//immutable in a document
var oldObj map[string]any
var err *u.Error
if entityStr == u.HIERARCHYOBJS_ENT {
oldObj, err = GetHierarchyObjectById(id, u.RequestFilters{}, userRoles)
if err == nil {
entityStr = oldObj["category"].(string)
}
} else {
oldObj, err = GetObject(idFilter, entityStr, u.RequestFilters{}, userRoles)
}
if err != nil {
return nil, err
//Update timestamp requires first obj retrieval
//there isn't any way for mongoDB to make a field
//immutable in a document
var oldObj map[string]any
var err *u.Error
if entityStr == u.HIERARCHYOBJS_ENT {
oldObj, err = GetHierarchyObjectById(id, u.RequestFilters{}, userRoles)
if err == nil {
entityStr = oldObj["category"].(string)
}
} else {
oldObj, err = GetObject(idFilter, entityStr, u.RequestFilters{}, userRoles)
}
if err != nil {
return nil, err
}

entity := u.EntityStrToInt(entityStr)
entity := u.EntityStrToInt(entityStr)

// Check if permission is only readonly
if u.IsEntityHierarchical(entity) && oldObj["description"] == nil {
// Description is always present, unless GetEntity was called with readonly permission
return nil, &u.Error{Type: u.ErrUnauthorized,
Message: "User does not have permission to change this object"}
}
// Check if permission is only readonly
if u.IsEntityHierarchical(entity) && oldObj["description"] == nil {
// Description is always present, unless GetEntity was called with readonly permission
return nil, &u.Error{Type: u.ErrUnauthorized,
Message: "User does not have permission to change this object"}
}

tags, tagsPresent := getTags(updateData)
tags, tagsPresent := getTags(updateData)

// Update old object data with patch data
if isPatch {
if tagsPresent {
return nil, &u.Error{
Type: u.ErrBadFormat,
Message: "Tags cannot be modified in this way, use tags+ and tags-",
}
// Update old object data with patch data
if isPatch {
if tagsPresent {
return nil, &u.Error{
Type: u.ErrBadFormat,
Message: "Tags cannot be modified in this way, use tags+ and tags-",
}
}

var formattedOldObj map[string]interface{}
// Convert primitive.A and similar types
bytes, _ := json.Marshal(oldObj)
json.Unmarshal(bytes, &formattedOldObj)
// Update old with new
err := updateOldObjWithPatch(formattedOldObj, updateData)
if err != nil {
return nil, &u.Error{Type: u.ErrBadFormat, Message: err.Error()}
}
var formattedOldObj map[string]interface{}
// Convert primitive.A and similar types
bytes, _ := json.Marshal(oldObj)
json.Unmarshal(bytes, &formattedOldObj)
// Update old with new
err := updateOldObjWithPatch(formattedOldObj, updateData)
if err != nil {
return nil, &u.Error{Type: u.ErrBadFormat, Message: err.Error()}
}

updateData = formattedOldObj
// Remove API set fields
delete(updateData, "id")
delete(updateData, "lastUpdated")
delete(updateData, "createdDate")
} else if tagsPresent {
err := verifyTagList(ctx, tags)
if err != nil {
return nil, err
}
updateData = formattedOldObj
// Remove API set fields
delete(updateData, "id")
delete(updateData, "lastUpdated")
delete(updateData, "createdDate")
} else if tagsPresent {
err := verifyTagList(tags)
if err != nil {
return nil, err
}
}

result, err := WithTransaction(func(ctx mongo.SessionContext) (interface{}, error) {
err = prepareUpdateObject(ctx, entity, id, updateData, oldObj, userRoles)
if err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions API/models/schemas/corridor_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
"type": "string",
"enum": ["m", "t", "f"]
},
"posZUnit": {
"type": "string",
"$ref": "refs/types.json#/definitions/metricImperialUnit"
},
"size": {
"type": "string",
"$ref": "refs/types.json#/definitions/vector2"
Expand Down
Loading

0 comments on commit ae6733c

Please sign in to comment.