diff --git a/api.yaml b/api.yaml index 304e4818..78088ebf 100755 --- a/api.yaml +++ b/api.yaml @@ -66,6 +66,7 @@ components: url: type: string format: uri + x-unique: true title: type: string description: diff --git a/controllers/rest/middleware_initialize.go b/controllers/rest/middleware_initialize.go index de2dbdde..3ba7826b 100644 --- a/controllers/rest/middleware_initialize.go +++ b/controllers/rest/middleware_initialize.go @@ -96,6 +96,15 @@ func newSchema(ref *openapi3.Schema, logger echo.Logger) (ds.Builder, map[string } } + uniquebytes, _ := json.Marshal(p.Value.Extensions["x-unique"]) + if len(uniquebytes) != 0 { + unique := false + json.Unmarshal(uniquebytes, &unique) + if unique { + gormParts = append(gormParts, "unique") + } + } + if strings.Contains(strings.Join(primaryKeys, " "), strings.ToLower(name)) { gormParts = append(gormParts, "primaryKey", "size:512") //only add NOT null if it's not already in the array to avoid issue if a user also add the field to the required array diff --git a/projections/gorm.go b/projections/gorm.go index 70b35fb3..4585f072 100644 --- a/projections/gorm.go +++ b/projections/gorm.go @@ -427,6 +427,21 @@ func (p *GORMDB) GetContentEntities(ctx context.Context, entityFactory weos.Enti return entities, count, result.Error } +func (p *GORMDB) GetByIdentifiers(ctxt context.Context, entityFactory weos.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { + results := entityFactory.Builder(ctxt).Build().NewSliceOfStructs() + result := p.db.Table(entityFactory.TableName()).Scopes(ContentQuery()).Find(results, identifiers) + if result.Error != nil { + p.logger.Errorf("unexpected error retrieving created blog, got: '%s'", result.Error) + } + bytes, err := json.Marshal(results) + if err != nil { + return nil, err + } + var entities []map[string]interface{} + json.Unmarshal(bytes, &entities) + return entities, nil +} + //DateTimeChecks checks to make sure the format is correctly as well as it manipulates the date func DateTimeCheck(entityFactory weos.EntityFactory, properties map[string]FilterProperty) (map[string]FilterProperty, error) { var err error @@ -532,7 +547,7 @@ func NewProjection(ctx context.Context, db *gorm.DB, logger weos.Log) (*GORMDB, ContentQuery = func() func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { if projection.db.Dialector.Name() == "sqlite" { - //gorm sqlite generates the query incorrectly if there are composite keys when preloading + //gorm sqlite generates the query incorrectly if there are composite keys when preloading. This may cause panics. //https://github.com/go-gorm/gorm/issues/3585 return db } else { diff --git a/projections/projections_test.go b/projections/projections_test.go index 03b91b87..0cf45293 100644 --- a/projections/projections_test.go +++ b/projections/projections_test.go @@ -4129,3 +4129,236 @@ components: t.Errorf("error removing table '%s' '%s'", "Blog", err) } } + +func TestProjections_GetByIdentifiers(t *testing.T) { + + t.Run("Get By Identifiers", func(t *testing.T) { + openAPI := `openapi: 3.0.3 +info: + title: Blog + description: Blog example + version: 1.0.0 +servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 +x-weos-config: + logger: + level: warn + report-caller: true + formatter: json + database: + driver: sqlite3 + database: test.db + event-source: + - title: default + driver: service + endpoint: https://prod1.weos.sh/events/v1 + - title: event + driver: sqlite3 + database: test.db + databases: + - title: default + driver: sqlite3 + database: test.db + rest: + middleware: + - RequestID + - Recover + - ZapLogger +components: + schemas: + Blog: + type: object + properties: + title: + type: string + description: blog title + description: + type: string + author: + type: string +` + + api, err := rest.New(openAPI) + if err != nil { + t.Fatalf("error loading api config '%s'", err) + } + + schemes := rest.CreateSchema(context.Background(), echo.New(), api.Swagger) + p, err := projections.NewProjection(context.Background(), gormDB, api.EchoInstance().Logger) + if err != nil { + t.Fatal(err) + } + + deletedFields := map[string][]string{} + for name, sch := range api.Swagger.Components.Schemas { + dfs, _ := json.Marshal(sch.Value.Extensions["x-remove"]) + json.Unmarshal(dfs, deletedFields[name]) + } + err = p.Migrate(context.Background(), schemes, deletedFields) + if err != nil { + t.Fatal(err) + } + + blogs := []map[string]interface{}{ + { + "title": "once", + "description": "twice", + "author": "once", + }, + { + "title": "twice", + "description": "once", + "author": "once", + }, + { + "title": "once", + "description": "once", + "author": "twice", + }, + } + result := gormDB.Table("Blog").Create(blogs) + if result.Error != nil { + t.Fatalf("got error creating fixtures '%s'", err) + } + + ctxt := context.Background() + ctxt = context.WithValue(ctxt, weosContext.ENTITY_FACTORY, new(weos.DefaultEntityFactory).FromSchemaAndBuilder("Blog", api.Swagger.Components.Schemas["Blog"].Value, schemes["Blog"])) + + blogEntityFactory := new(weos.DefaultEntityFactory).FromSchemaAndBuilder("Blog", api.Swagger.Components.Schemas["Blog"].Value, schemes["Blog"]) + r, err := p.GetByIdentifiers(ctxt, blogEntityFactory, map[string]interface{}{"author": "twice"}) + if err != nil { + t.Errorf("got error retrieving blogs '%s'", err) + } + if len(r) != 1 { + t.Errorf("expected to get %d blogs, got %d", 1, len(r)) + } + + if r[0]["title"] != blogs[2]["title"] || r[0]["description"] != blogs[2]["description"] || r[0]["author"] != blogs[2]["author"] { + t.Errorf("expected blog to be %v got %v", blogs[2], r[0]) + } + + r, err = p.GetByIdentifiers(ctxt, blogEntityFactory, map[string]interface{}{"description": "twice"}) + if err != nil { + t.Errorf("got error retrieving blogs '%s'", err) + } + if len(r) != 1 { + t.Errorf("expected to get %d blogs, got %d", 1, len(r)) + } + + if r[0]["title"] != blogs[0]["title"] || r[0]["description"] != blogs[0]["description"] || r[0]["author"] != blogs[0]["author"] { + t.Errorf("expected blog to be %v got %v", blogs[2], r[0]) + } + + r, err = p.GetByIdentifiers(ctxt, blogEntityFactory, map[string]interface{}{"title": "once"}) + if err != nil { + t.Errorf("got error retrieving blogs '%s'", err) + } + if len(r) != 2 { + t.Errorf("expected to get %d blogs, got %d", 2, len(r)) + } + + err = gormDB.Migrator().DropTable("Blog") + if err != nil { + t.Errorf("error removing table '%s' '%s'", "Blog", err) + } + }) +} + +func TestProjections_XUnique(t *testing.T) { + + t.Run("Test unique fields", func(t *testing.T) { + openAPI := `openapi: 3.0.3 +info: + title: Blog + description: Blog example + version: 1.0.0 +servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 +x-weos-config: + logger: + level: warn + report-caller: true + formatter: json + database: + driver: sqlite3 + database: test.db + event-source: + - title: default + driver: service + endpoint: https://prod1.weos.sh/events/v1 + - title: event + driver: sqlite3 + database: test.db + databases: + - title: default + driver: sqlite3 + database: test.db + rest: + middleware: + - RequestID + - Recover + - ZapLogger +components: + schemas: + Blog: + type: object + properties: + title: + type: string + description: blog title + x-unique: true + description: + type: string + author: + type: string +` + + api, err := rest.New(openAPI) + if err != nil { + t.Fatalf("error loading api config '%s'", err) + } + + schemes := rest.CreateSchema(context.Background(), echo.New(), api.Swagger) + p, err := projections.NewProjection(context.Background(), gormDB, api.EchoInstance().Logger) + if err != nil { + t.Fatal(err) + } + + deletedFields := map[string][]string{} + for name, sch := range api.Swagger.Components.Schemas { + dfs, _ := json.Marshal(sch.Value.Extensions["x-remove"]) + json.Unmarshal(dfs, deletedFields[name]) + } + err = p.Migrate(context.Background(), schemes, deletedFields) + if err != nil { + t.Fatal(err) + } + + blogs := []map[string]interface{}{ + { + "title": "once", + "description": "twice", + "author": "once", + }, + { + "title": "twice", + "description": "once", + "author": "once", + }, + { + "title": "once", + "description": "once", + "author": "twice", + }, + } + result := gormDB.Table("Blog").Create(blogs) + if result.Error == nil { + t.Fatalf("expected to get unique error on title 'once'") + } + + }) +}