diff --git a/server/cmd/db-migrations/common_test.go b/server/cmd/db-migrations/common_test.go new file mode 100644 index 0000000000..cb3e117ac5 --- /dev/null +++ b/server/cmd/db-migrations/common_test.go @@ -0,0 +1,7 @@ +package main + +import "github.com/reearth/reearthx/mongox/mongotest" + +func init() { + mongotest.Env = "REEARTH_CMS_DB" +} diff --git a/server/cmd/db-migrations/main.go b/server/cmd/db-migrations/main.go new file mode 100644 index 0000000000..004bde31dc --- /dev/null +++ b/server/cmd/db-migrations/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "github.com/joho/godotenv" +) + +type command = func(ctx context.Context, dbURL, dbName string, wetRun bool) error + +var commands = map[string]command{ + "ref-field-schema": RefFieldSchema, +} + +func main() { + wet := flag.Bool("wet-run", false, "wet run (default: dry-run)") + cmd := flag.String("cmd", "", "migration to be executed name") + flag.Parse() + + if *cmd == "" { + fmt.Print("command is not set") + return + } + + command := commands[*cmd] + if command == nil { + fmt.Printf("command '%s' not found", *cmd) + return + } + + // load .env + if err := godotenv.Load(".env"); err != nil && !os.IsNotExist(err) { + fmt.Printf("load .env failed: %s\n", err) + return + } else if err == nil { + fmt.Printf("config: .env loaded\n") + } + + // get db url + dbURL := os.Getenv("REEARTH_CMS_DB") + if dbURL == "" { + fmt.Print("REEARTH_CMS_DB is not set") + return + } + + // exec command + fmt.Printf("command: '%s' ", *cmd) + ctx := context.Background() + if err := command(ctx, dbURL, "reearth_cms", *wet); err != nil { + fmt.Printf("faild: %s.\n", err) + return + } + fmt.Printf("succeeded.\n") +} diff --git a/server/cmd/db-migrations/ref_field_schema.go b/server/cmd/db-migrations/ref_field_schema.go new file mode 100644 index 0000000000..a43caed6e0 --- /dev/null +++ b/server/cmd/db-migrations/ref_field_schema.go @@ -0,0 +1,178 @@ +package main + +import ( + "context" + "fmt" + + "github.com/samber/lo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type Schema struct { + ID string `bson:"id"` + Fields []*Field `bson:"fields"` +} + +type Field struct { + ID string `bson:"id"` + TypeProperty *TypeProperty `bson:"typeproperty"` +} + +type TypeProperty struct { + Type string `bson:"type"` + Reference *Reference `bson:"reference"` +} + +type Reference struct { + Model string `bson:"model"` + Schema string `bson:"schema"` + CorrespondingSchema *string `bson:"correspondingschema"` +} + +func (r *Reference) SetSchema(s string) { + r.Schema = s +} + +type Model struct { + ID string `bson:"id"` + Schema string `bson:"schema"` +} + +func RefFieldSchema(ctx context.Context, dbURL, dbName string, wetRun bool) error { + testID := "" + + client, err := mongo.Connect(ctx, options.Client().ApplyURI(dbURL)) + if err != nil { + return fmt.Errorf("db: failed to init client err: %w", err) + } + sCol := client.Database(dbName).Collection("schema") + mCol := client.Database(dbName).Collection("model") + + schemas, err := loadSchemas(ctx, sCol) + if err != nil { + return err + } + + if len(schemas) == 0 { + return fmt.Errorf("no docs found") + } + + mIds := lo.FlatMap(schemas, func(s Schema, _ int) []string { + return lo.FilterMap(s.Fields, func(f *Field, _ int) (string, bool) { + if f.TypeProperty.Type != "reference" { + return "", false + } + return f.TypeProperty.Reference.Model, true + }) + }) + models, err := loadModels(ctx, mIds, mCol) + if err != nil { + return err + } + + fmt.Printf("%d docs found, first id: %s\n", len(schemas), schemas[0].ID) + + if testID != "" { + schemas = lo.Filter(schemas, func(s Schema, _ int) bool { + return s.ID == testID + }) + fmt.Printf("test mode: filter on '%s', now %d docs selcted\n", testID, len(schemas)) + } + + lo.ForEach(schemas, func(s Schema, _ int) { + lo.ForEach(s.Fields, func(f *Field, _ int) { + if f.TypeProperty.Type != "reference" { + return + } + m, ok := models[f.TypeProperty.Reference.Model] + if ok { + f.TypeProperty.Reference.SetSchema(m.Schema) + return + } + if f.TypeProperty.Reference.CorrespondingSchema != nil { + f.TypeProperty.Reference.SetSchema(*f.TypeProperty.Reference.CorrespondingSchema) + return + } + fmt.Printf("no model found for schema '%s' model id '%s'\n", s.ID, f.TypeProperty.Reference.Model) + }) + }) + + // update all documents in col + writes := lo.FlatMap(schemas, func(s Schema, _ int) []mongo.WriteModel { + return lo.FilterMap(s.Fields, func(f *Field, _ int) (mongo.WriteModel, bool) { + if f.TypeProperty.Type != "reference" { + return nil, false + } + fmt.Printf("updating schema '%s' field '%s' referenced schema '%s'\n", s.ID, f.ID, f.TypeProperty.Reference.Schema) + return mongo.NewUpdateOneModel(). + SetFilter(bson.M{ + "id": s.ID, + "fields.id": f.ID, + }). + SetUpdate(bson.M{ + "$set": bson.M{ + "fields.$[f].typeproperty.reference.schema": f.TypeProperty.Reference.Schema, + }, + }). + SetArrayFilters(options.ArrayFilters{ + Filters: []interface{}{bson.M{"f.id": f.ID}}, + }), true + }) + }) + + if !wetRun { + fmt.Printf("dry run\n") + fmt.Printf("%d docs will be updated\n", len(writes)) + return nil + } + + fmt.Printf("writing docs...") + res, err := sCol.BulkWrite(ctx, writes) + if err != nil { + return fmt.Errorf("failed to bulk write: %w", err) + } + + fmt.Printf("%d docs updated\n", res.ModifiedCount) + return nil +} + +func loadSchemas(ctx context.Context, col *mongo.Collection) ([]Schema, error) { + cur, err := col.Find( + ctx, + bson.M{"fields.typeproperty.type": "reference"}, + options.Find().SetProjection(bson.M{"id": 1, "fields": 1}), + ) + if err != nil { + return nil, fmt.Errorf("failed to find schemas docs: %w", err) + } + + var schemas []Schema + err = cur.All(ctx, &schemas) + if err != nil { + return nil, fmt.Errorf("failed to decode schemas docs: %w", err) + } + return schemas, nil +} + +func loadModels(ctx context.Context, sIDs []string, col *mongo.Collection) (map[string]Model, error) { + cur, err := col.Find( + ctx, + bson.M{"id": bson.M{"$in": sIDs}}, + options.Find().SetProjection(bson.M{"id": 1, "schema": 1}), + ) + if err != nil { + return nil, fmt.Errorf("failed to find models docs: %w", err) + } + + var models []Model + err = cur.All(ctx, &models) + if err != nil { + return nil, fmt.Errorf("failed to decode models docs: %w", err) + } + return lo.SliceToMap(models, func(m Model) (string, Model) { + return m.ID, m + + }), nil +} diff --git a/server/cmd/db-migrations/ref_field_schema_test.go b/server/cmd/db-migrations/ref_field_schema_test.go new file mode 100644 index 0000000000..0fa3dea398 --- /dev/null +++ b/server/cmd/db-migrations/ref_field_schema_test.go @@ -0,0 +1,153 @@ +package main + +import ( + "context" + "os" + "testing" + + "github.com/reearth/reearth-cms/server/internal/infrastructure/mongo/mongodoc" + "github.com/reearth/reearth-cms/server/pkg/id" + "github.com/reearth/reearthx/log" + "github.com/reearth/reearthx/mongox/mongotest" + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func TestRefFieldSchema(t *testing.T) { + s1ID, s2ID, s3ID := id.NewSchemaID().String(), id.NewSchemaID().String(), id.NewSchemaID().String() + m1ID, m2ID, m3ID := id.NewModelID().String(), id.NewModelID().String(), id.NewModelID().String() + s1 := map[string]any{ + "id": s1ID, + "fields": []map[string]any{ + { + "id": "1", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m2ID, + "correspondingschema": s2ID, + }, + }, + }, + { + "id": "2", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m3ID, + "correspondingschema": s3ID, + "correspondingfield": "3", + }, + }, + }, + }, + } + m1 := map[string]any{ + "id": m1ID, + "schema": s1ID, + } + s2 := map[string]any{ + "id": s2ID, + "fields": nil, + } + m2 := map[string]any{ + "id": m2ID, + "schema": s2ID, + } + s3 := map[string]any{ + "id": s3ID, + "fields": []map[string]any{ + { + "id": "3", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m1ID, + "correspondingschema": s1ID, + "correspondingfield": "2", + }, + }, + }, + }, + } + m3 := map[string]any{ + "id": m3ID, + "schema": s3ID, + } + + db := mongotest.Connect(t)(t) + log.Infof("test: new db created with name: %v", db.Name()) + + ctx := context.Background() + sCol := db.Collection("schema") + mCol := db.Collection("model") + + _, err := sCol.InsertMany(ctx, []any{s1, s2, s3}) + assert.NoError(t, err) + + _, err = mCol.InsertMany(ctx, []any{m1, m2, m3}) + assert.NoError(t, err) + + err = RefFieldSchema(ctx, os.Getenv("REEARTH_CMS_DB"), db.Name(), true) + assert.NoError(t, err) + + s1Updated := map[string]any{} + err = sCol.FindOne(ctx, bson.M{"id": s1ID}).Decode(&s1Updated) + assert.NoError(t, err) + assert.Equal(t, map[string]any{ + "_id": s1Updated["_id"], + "id": s1ID, + "fields": primitive.A{ + map[string]any{ + "id": "1", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m2ID, + "schema": s2ID, + "correspondingschema": s2ID, + }, + }, + }, + map[string]any{ + "id": "2", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m3ID, + "schema": s3ID, + "correspondingschema": s3ID, + "correspondingfield": "3", + }, + }, + }, + }, + }, s1Updated) + + s2Updated := mongodoc.SchemaDocument{} + err = sCol.FindOne(ctx, bson.M{"id": s2ID}).Decode(&s2Updated) + assert.NoError(t, err) + + s3Updated := map[string]any{} + err = sCol.FindOne(ctx, bson.M{"id": s3ID}).Decode(&s3Updated) + assert.NoError(t, err) + assert.Equal(t, map[string]any{ + "_id": s3Updated["_id"], + "id": s3ID, + "fields": primitive.A{ + map[string]any{ + "id": "3", + "typeproperty": map[string]any{ + "type": "reference", + "reference": map[string]any{ + "model": m1ID, + "schema": s1ID, + "correspondingschema": s1ID, + "correspondingfield": "2", + }, + }, + }, + }, + }, s3Updated) +} diff --git a/server/e2e/gql_field_test.go b/server/e2e/gql_field_test.go index 282d8519e4..419d838133 100644 --- a/server/e2e/gql_field_test.go +++ b/server/e2e/gql_field_test.go @@ -39,11 +39,14 @@ func createField(e *httpexpect.Expect, mID, title, desc, key string, multiple, u WithHeader("X-Reearth-Debug-User", uId1.String()). WithHeader("Content-Type", "application/json"). WithJSON(requestBody). - Expect(). - Status(http.StatusOK). - JSON() + Expect() - return res.Path("$.data.createField.field.id").Raw().(string), res + if res.Raw().StatusCode != http.StatusOK { + res.JSON().IsNull() + } + + json := res.JSON() + return json.Path("$.data.createField.field.id").Raw().(string), json } func createMetaField(e *httpexpect.Expect, mID, title, desc, key string, multiple, unique, isTitle, required bool, fType string, fTypeProp map[string]any) (string, *httpexpect.Value) { diff --git a/server/e2e/gql_item_test.go b/server/e2e/gql_item_test.go index 578976a1c0..591497833c 100644 --- a/server/e2e/gql_item_test.go +++ b/server/e2e/gql_item_test.go @@ -469,7 +469,8 @@ func TestTwoWayReferenceFields(t *testing.T) { false, false, false, false, "Reference", map[string]any{ "reference": map[string]any{ - "modelId": m1Id, + "modelId": m1Id, + "schemaId": s1Id, "correspondingField": map[string]any{ "title": "Ref to test 1", "key": "test-1-ref", diff --git a/server/e2e/integration_item_test.go b/server/e2e/integration_item_test.go index abf74d5851..1edca66272 100644 --- a/server/e2e/integration_item_test.go +++ b/server/e2e/integration_item_test.go @@ -137,7 +137,7 @@ func baseSeeder(ctx context.Context, r *repo.Container) error { // region schema & model sf1 := schema.NewField(schema.NewText(nil).TypeProperty()).ID(fId1).Key(sfKey1).MustBuild() sf2 := schema.NewField(schema.NewAsset().TypeProperty()).ID(fId2).Key(sfKey2).MustBuild() - sf3 := schema.NewField(schema.NewReference(mId1, nil, nil, nil).TypeProperty()).ID(fId3).Key(sfKey3).MustBuild() + sf3 := schema.NewField(schema.NewReference(mId1, sid1, nil, nil).TypeProperty()).ID(fId3).Key(sfKey3).MustBuild() sf4 := schema.NewField(schema.NewBool().TypeProperty()).ID(fId4).Key(sfKey4).MustBuild() s1 := schema.New().ID(sid1).Workspace(w.ID()).Project(p.ID()).Fields([]*schema.Field{sf1, sf2}).TitleField(sf1.ID().Ref()).MustBuild() diff --git a/server/go.sum b/server/go.sum index 1cfbe17ce2..02494fa08c 100644 --- a/server/go.sum +++ b/server/go.sum @@ -312,8 +312,6 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/ravilushqa/otelgqlgen v0.15.0 h1:U85nrlweMXTGaMChUViYM39/MXBZVeVVlpuHq+6eECQ= github.com/ravilushqa/otelgqlgen v0.15.0/go.mod h1:o+1Eju0VySmgq2BP8Vupz2YrN21Bj7D7imBqu3m2uB8= -github.com/reearth/reearthx v0.0.0-20240201064205-4a2be423e40d h1:OWU1s9hnxc7pfQ55kbc7M6SndYIM/hWxkeytDq8L/Jc= -github.com/reearth/reearthx v0.0.0-20240201064205-4a2be423e40d/go.mod h1:8DSD6e+h1KhfhO4TceYDDPpLkhZJH2CLF6nHxyV8hts= github.com/robbiet480/go.sns v0.0.0-20230523235941-e8d832c79d68 h1:Jknsfy5cqCH6qAuoU1qNZ51hfBJfMSJYwsH9j9mdVnw= github.com/robbiet480/go.sns v0.0.0-20230523235941-e8d832c79d68/go.mod h1:9CDhL7uDVy8vEVDNPJzxq89dPaPBWP6hxQcC8woBHus= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -415,8 +413,6 @@ go.opentelemetry.io/contrib v1.22.0 h1:QflN9z334UrOPzGGEr8VaMlWm+i+d9YLW8KzQtbvm go.opentelemetry.io/contrib v1.22.0/go.mod h1:usW9bPlrjHiJFbK0a6yK/M5wNHs3nLmtrT3vzhoD3co= go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.47.0 h1:LxU1CtJeUgR3sSIoEqTWuJ1VFAgybxpqKZjeTAFvDfo= go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.47.0/go.mod h1:kNOJ6ovdGbJ/L8Oq4+5yftrkp78Z8V4M8H9aJcMe46w= -go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.47.0 h1:1ahNAu2+hiHJOXd9J8hQ1zSGxEYHy7sn1ozpL50YWZY= -go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.47.0/go.mod h1:VEW8hmKJJZg+c3lfqHhxqa0BYg2PEUyNRehU5D2yBDw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= diff --git a/server/gqlgen.yml b/server/gqlgen.yml index bb4c6d42a0..fd47f8eda8 100644 --- a/server/gqlgen.yml +++ b/server/gqlgen.yml @@ -99,9 +99,9 @@ models: resolver: true SchemaFieldReference: fields: - correspondingField: + schema: resolver: true - correspondingSchema: + correspondingField: resolver: true Asset: fields: diff --git a/server/i18n/en.yml b/server/i18n/en.yml index e29a1ae02a..6ad24790c5 100644 --- a/server/i18n/en.yml +++ b/server/i18n/en.yml @@ -60,6 +60,7 @@ metadata item and schema mismatch: "" metadata schema not found: "" model key is already used by another model: "" model should have at least one view: "" +multiple reference is not supported: "" not found: "" not implemented: "" not implemented yet: "" @@ -72,6 +73,9 @@ operation denied: "" project alias is already used by another project: "" project alias is not set: "" projectID is required: "" +reference field direction can not be changed: "" +reference field model can not be changed: "" +referenced field key exists: "" reviewer should be owner or maintainer: "" thread is required: "" title cannot be empty: "" diff --git a/server/i18n/ja.yml b/server/i18n/ja.yml index 460c560bb5..097b00cdc7 100644 --- a/server/i18n/ja.yml +++ b/server/i18n/ja.yml @@ -9,14 +9,14 @@ auth0 is not set up: Auth0が設定されていません。 "auth0: domain is not set": Auth0のドメインが設定されていません。 bucket name is empty: ストレージバケット名が空白です。 can't delete a group as it's used by some models: いくつかのモデルで使用されているため、グループを削除できません。 -can't update by approve: このメソッドでApproveすることはできません +can't update by approve: このメソッドで承認することはできません comment already exist in this thread: コメントは既にこのスレッドに存在します。 comment does not exist in this thread: コメントはこのスレッドに存在しません。 comment not found: コメントが見つかりませんでした。 createdBy is required: createdByは必須です。 -duplicated item: 重複したアイテム +duplicated item: アイテムが重複しています。 duplicated key: キーが重複しています。 -duplicated value: 値が重複すています。 +duplicated value: 値が重複しています。 either model or group should be provided: モデルまたはグループのどちらかを指定してください。 failed to auth: 認証に失敗しました。 failed to create asset: アセットの作成に失敗しました。 @@ -24,7 +24,7 @@ failed to delete file: ファイルの削除に失敗しました。 failed to lock: ロックに失敗しました。 failed to update user: ユーザー情報の更新に失敗しました。 failed to upload file: ファイルのアップロードに失敗しました。 -field not found: ファイルが見つかりませんでした。 +field not found: フィールドが見つかりませんでした。 field value exist: フィールドの値はすでに存在します。 file not found: ファイルが見つかりませんでした。 file not included: ファイルが含まれていません。 @@ -60,6 +60,7 @@ metadata item and schema mismatch: メタデータのアイテムのスキーマ metadata schema not found: メタデータのスキーマが見つかりません。 model key is already used by another model: このキーはすでに別のモデルで使用されています。 model should have at least one view: モデルには少なくとも 1 つのビューが必要です +multiple reference is not supported: 複数参照はサポートされていません not found: 見つかりませんでした。 not implemented: 未実装です。 not implemented yet: 未実装です。 @@ -72,6 +73,9 @@ operation denied: 操作が拒否されました。 project alias is already used by another project: プロジェクトエイリアスはすでに別のプロジェクトで使用されています。 project alias is not set: プロジェクトエイリアスが設定されていません。 projectID is required: プロジェクトIDは必須です。 +reference field direction can not be changed: 参照フィールドの方向は変更できません +reference field model can not be changed: 参照フィールドのモデルは変更できません +referenced field key exists: 参照フィールドのキーがすでに存在します reviewer should be owner or maintainer: レビュワーはオーナーもしくはメインテイナーである必要があります。 thread is required: スレッドは必須です。 title cannot be empty: タイトルは必須です。 diff --git a/server/internal/adapter/gql/generated.go b/server/internal/adapter/gql/generated.go index fe41d5edfc..c679daf510 100644 --- a/server/internal/adapter/gql/generated.go +++ b/server/internal/adapter/gql/generated.go @@ -667,11 +667,11 @@ type ComplexityRoot struct { } SchemaFieldReference struct { - CorrespondingField func(childComplexity int) int - CorrespondingFieldID func(childComplexity int) int - CorrespondingSchema func(childComplexity int) int - CorrespondingSchemaID func(childComplexity int) int - ModelID func(childComplexity int) int + CorrespondingField func(childComplexity int) int + CorrespondingFieldID func(childComplexity int) int + ModelID func(childComplexity int) int + Schema func(childComplexity int) int + SchemaID func(childComplexity int) int } SchemaFieldRichText struct { @@ -999,7 +999,7 @@ type SchemaFieldResolver interface { Model(ctx context.Context, obj *gqlmodel.SchemaField) (*gqlmodel.Model, error) } type SchemaFieldReferenceResolver interface { - CorrespondingSchema(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.Schema, error) + Schema(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.Schema, error) CorrespondingField(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.SchemaField, error) } @@ -3878,26 +3878,26 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.SchemaFieldReference.CorrespondingFieldID(childComplexity), true - case "SchemaFieldReference.correspondingSchema": - if e.complexity.SchemaFieldReference.CorrespondingSchema == nil { + case "SchemaFieldReference.modelId": + if e.complexity.SchemaFieldReference.ModelID == nil { break } - return e.complexity.SchemaFieldReference.CorrespondingSchema(childComplexity), true + return e.complexity.SchemaFieldReference.ModelID(childComplexity), true - case "SchemaFieldReference.correspondingSchemaId": - if e.complexity.SchemaFieldReference.CorrespondingSchemaID == nil { + case "SchemaFieldReference.schema": + if e.complexity.SchemaFieldReference.Schema == nil { break } - return e.complexity.SchemaFieldReference.CorrespondingSchemaID(childComplexity), true + return e.complexity.SchemaFieldReference.Schema(childComplexity), true - case "SchemaFieldReference.modelId": - if e.complexity.SchemaFieldReference.ModelID == nil { + case "SchemaFieldReference.schemaId": + if e.complexity.SchemaFieldReference.SchemaID == nil { break } - return e.complexity.SchemaFieldReference.ModelID(childComplexity), true + return e.complexity.SchemaFieldReference.SchemaID(childComplexity), true case "SchemaFieldRichText.defaultValue": if e.complexity.SchemaFieldRichText.DefaultValue == nil { @@ -5081,8 +5081,8 @@ type SchemaFieldInteger { type SchemaFieldReference { modelId: ID! - correspondingSchemaId: ID - correspondingSchema: Schema + schemaId: ID! + schema: Schema! correspondingFieldId: ID correspondingField: SchemaField } @@ -5161,15 +5161,15 @@ input SchemaFieldIntegerInput { input CorrespondingFieldInput { fieldId: ID - title: String - key: String - description: String - required: Boolean + title: String! + key: String! + description: String! + required: Boolean! } input SchemaFieldReferenceInput { modelId: ID! - correspondingSchemaId: ID + schemaId: ID! correspondingField: CorrespondingFieldInput } @@ -26127,8 +26127,8 @@ func (ec *executionContext) fieldContext_SchemaFieldReference_modelId(ctx contex return fc, nil } -func (ec *executionContext) _SchemaFieldReference_correspondingSchemaId(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldReference) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SchemaFieldReference_correspondingSchemaId(ctx, field) +func (ec *executionContext) _SchemaFieldReference_schemaId(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldReference) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SchemaFieldReference_schemaId(ctx, field) if err != nil { return graphql.Null } @@ -26141,21 +26141,24 @@ func (ec *executionContext) _SchemaFieldReference_correspondingSchemaId(ctx cont }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.CorrespondingSchemaID, nil + return obj.SchemaID, nil }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } - res := resTmp.(*gqlmodel.ID) + res := resTmp.(gqlmodel.ID) fc.Result = res - return ec.marshalOID2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, field.Selections, res) + return ec.marshalNID2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SchemaFieldReference_correspondingSchemaId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_SchemaFieldReference_schemaId(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "SchemaFieldReference", Field: field, @@ -26168,8 +26171,8 @@ func (ec *executionContext) fieldContext_SchemaFieldReference_correspondingSchem return fc, nil } -func (ec *executionContext) _SchemaFieldReference_correspondingSchema(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldReference) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_SchemaFieldReference_correspondingSchema(ctx, field) +func (ec *executionContext) _SchemaFieldReference_schema(ctx context.Context, field graphql.CollectedField, obj *gqlmodel.SchemaFieldReference) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_SchemaFieldReference_schema(ctx, field) if err != nil { return graphql.Null } @@ -26182,21 +26185,24 @@ func (ec *executionContext) _SchemaFieldReference_correspondingSchema(ctx contex }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.SchemaFieldReference().CorrespondingSchema(rctx, obj) + return ec.resolvers.SchemaFieldReference().Schema(rctx, obj) }) if err != nil { ec.Error(ctx, err) return graphql.Null } if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } return graphql.Null } res := resTmp.(*gqlmodel.Schema) fc.Result = res - return ec.marshalOSchema2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchema(ctx, field.Selections, res) + return ec.marshalNSchema2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐSchema(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_SchemaFieldReference_correspondingSchema(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_SchemaFieldReference_schema(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "SchemaFieldReference", Field: field, @@ -32939,28 +32945,28 @@ func (ec *executionContext) unmarshalInputCorrespondingFieldInput(ctx context.Co it.FieldID = data case "title": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalNString2string(ctx, v) if err != nil { return it, err } it.Title = data case "key": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("key")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalNString2string(ctx, v) if err != nil { return it, err } it.Key = data case "description": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("description")) - data, err := ec.unmarshalOString2ᚖstring(ctx, v) + data, err := ec.unmarshalNString2string(ctx, v) if err != nil { return it, err } it.Description = data case "required": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("required")) - data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + data, err := ec.unmarshalNBoolean2bool(ctx, v) if err != nil { return it, err } @@ -35181,7 +35187,7 @@ func (ec *executionContext) unmarshalInputSchemaFieldReferenceInput(ctx context. asMap[k] = v } - fieldsInOrder := [...]string{"modelId", "correspondingSchemaId", "correspondingField"} + fieldsInOrder := [...]string{"modelId", "schemaId", "correspondingField"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -35195,13 +35201,13 @@ func (ec *executionContext) unmarshalInputSchemaFieldReferenceInput(ctx context. return it, err } it.ModelID = data - case "correspondingSchemaId": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("correspondingSchemaId")) - data, err := ec.unmarshalOID2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, v) + case "schemaId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("schemaId")) + data, err := ec.unmarshalNID2githubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐID(ctx, v) if err != nil { return it, err } - it.CorrespondingSchemaID = data + it.SchemaID = data case "correspondingField": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("correspondingField")) data, err := ec.unmarshalOCorrespondingFieldInput2ᚖgithubᚗcomᚋreearthᚋreearthᚑcmsᚋserverᚋinternalᚋadapterᚋgqlᚋgqlmodelᚐCorrespondingFieldInput(ctx, v) @@ -43269,9 +43275,12 @@ func (ec *executionContext) _SchemaFieldReference(ctx context.Context, sel ast.S if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "correspondingSchemaId": - out.Values[i] = ec._SchemaFieldReference_correspondingSchemaId(ctx, field, obj) - case "correspondingSchema": + case "schemaId": + out.Values[i] = ec._SchemaFieldReference_schemaId(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "schema": field := field innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { @@ -43280,7 +43289,10 @@ func (ec *executionContext) _SchemaFieldReference(ctx context.Context, sel ast.S ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._SchemaFieldReference_correspondingSchema(ctx, field, obj) + res = ec._SchemaFieldReference_schema(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } return res } diff --git a/server/internal/adapter/gql/gqlmodel/convert_schema.go b/server/internal/adapter/gql/gqlmodel/convert_schema.go index efaaec4dc4..5e2e84762f 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_schema.go +++ b/server/internal/adapter/gql/gqlmodel/convert_schema.go @@ -214,9 +214,9 @@ func ToSchemaFieldTypeProperty(tp *schema.TypeProperty, dv *value.Multiple, mult }, Reference: func(f *schema.FieldReference) { res = &SchemaFieldReference{ - ModelID: IDFrom(f.Model()), - CorrespondingSchemaID: IDFromRef(f.CorrespondingSchema()), - CorrespondingFieldID: IDFromRef(f.CorrespondingFieldID()), + ModelID: IDFrom(f.Model()), + SchemaID: IDFrom(f.Schema()), + CorrespondingFieldID: IDFromRef(f.CorrespondingFieldID()), } }, URL: func(f *schema.FieldURL) { @@ -252,6 +252,7 @@ func valueString(dv *value.Multiple, multiple bool) any { } var ErrInvalidTypeProperty = rerror.NewE(i18n.T("invalid type property")) +var ErrMultipleReference = rerror.NewE(i18n.T("multiple reference is not supported")) var ErrEmptyOptions = rerror.NewE(i18n.T("Options could not be empty!")) func FromCorrespondingField(cf *CorrespondingFieldInput) *schema.CorrespondingField { @@ -260,7 +261,6 @@ func FromCorrespondingField(cf *CorrespondingFieldInput) *schema.CorrespondingFi } return &schema.CorrespondingField{ - FieldID: ToIDRef[id.Field](cf.FieldID), Title: cf.Title, Key: cf.Key, Description: cf.Description, @@ -452,19 +452,22 @@ func FromSchemaTypeProperty(tp *SchemaFieldTypePropertyInput, t SchemaFieldType, if x == nil { return nil, nil, ErrInvalidTypeProperty } + if multiple { + return nil, nil, ErrMultipleReference + } mId, err := ToID[id.Model](x.ModelID) if err != nil { return nil, nil, err } + sId, err := ToID[id.Schema](x.SchemaID) + if err != nil { + return nil, nil, err + } var fid *id.FieldID if x.CorrespondingField != nil { fid = ToIDRef[id.Field](x.CorrespondingField.FieldID) } - var sid *id.SchemaID - if x.CorrespondingSchemaID != nil { - sid = ToIDRef[id.Schema](x.CorrespondingSchemaID) - } - tpRes = schema.NewReference(mId, sid, FromCorrespondingField(x.CorrespondingField), fid).TypeProperty() + tpRes = schema.NewReference(mId, sId, fid, FromCorrespondingField(x.CorrespondingField)).TypeProperty() case SchemaFieldTypeGroup: x := tp.Group if x == nil { diff --git a/server/internal/adapter/gql/gqlmodel/convert_schema_test.go b/server/internal/adapter/gql/gqlmodel/convert_schema_test.go index ed1d4d5aab..b9dcc74814 100644 --- a/server/internal/adapter/gql/gqlmodel/convert_schema_test.go +++ b/server/internal/adapter/gql/gqlmodel/convert_schema_test.go @@ -128,6 +128,7 @@ func TestToSchemaField(t *testing.T) { func TestToSchemaFieldTypeProperty(t *testing.T) { mid := id.NewModelID() + sid := id.NewSchemaID() type args struct { tp *schema.TypeProperty @@ -180,8 +181,8 @@ func TestToSchemaFieldTypeProperty(t *testing.T) { }, { name: "reference", - args: args{tp: schema.NewReference(mid, nil, nil, nil).TypeProperty()}, - want: &SchemaFieldReference{ModelID: IDFrom(mid)}, + args: args{tp: schema.NewReference(mid, sid, nil, nil).TypeProperty()}, + want: &SchemaFieldReference{ModelID: IDFrom(mid), SchemaID: IDFrom(sid)}, }, { name: "asset", @@ -221,6 +222,7 @@ func TestToSchemaFieldTypeProperty(t *testing.T) { func TestFromSchemaFieldTypeProperty(t *testing.T) { mid := id.NewModelID() + sid := id.NewSchemaID() tests := []struct { name string @@ -289,11 +291,12 @@ func TestFromSchemaFieldTypeProperty(t *testing.T) { name: "reference", argsInp: &SchemaFieldTypePropertyInput{ Reference: &SchemaFieldReferenceInput{ - ModelID: ID(mid.String()), + ModelID: ID(mid.String()), + SchemaID: ID(sid.String()), }, }, argsT: SchemaFieldTypeReference, - wantTp: schema.NewReference(mid, nil, nil, nil).TypeProperty(), + wantTp: schema.NewReference(mid, sid, nil, nil).TypeProperty(), }, { name: "asset", @@ -360,13 +363,12 @@ func TestFromCorrespondingField(t *testing.T) { cf = &CorrespondingFieldInput{ FieldID: IDFromRef(id.NewFieldID().Ref()), - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr(""), - Required: lo.ToPtr(false), + Title: "title", + Key: "key", + Description: "", + Required: false, } want := &schema.CorrespondingField{ - FieldID: ToIDRef[id.Field](cf.FieldID), Title: cf.Title, Key: cf.Key, Description: cf.Description, diff --git a/server/internal/adapter/gql/gqlmodel/models_gen.go b/server/internal/adapter/gql/gqlmodel/models_gen.go index a13263ad2b..94412d05c9 100644 --- a/server/internal/adapter/gql/gqlmodel/models_gen.go +++ b/server/internal/adapter/gql/gqlmodel/models_gen.go @@ -208,11 +208,11 @@ type ConditionInput struct { } type CorrespondingFieldInput struct { - FieldID *ID `json:"fieldId,omitempty"` - Title *string `json:"title,omitempty"` - Key *string `json:"key,omitempty"` - Description *string `json:"description,omitempty"` - Required *bool `json:"required,omitempty"` + FieldID *ID `json:"fieldId,omitempty"` + Title string `json:"title"` + Key string `json:"key"` + Description string `json:"description"` + Required bool `json:"required"` } type CreateAssetInput struct { @@ -987,19 +987,19 @@ type SchemaFieldMarkdown struct { func (SchemaFieldMarkdown) IsSchemaFieldTypeProperty() {} type SchemaFieldReference struct { - ModelID ID `json:"modelId"` - CorrespondingSchemaID *ID `json:"correspondingSchemaId,omitempty"` - CorrespondingSchema *Schema `json:"correspondingSchema,omitempty"` - CorrespondingFieldID *ID `json:"correspondingFieldId,omitempty"` - CorrespondingField *SchemaField `json:"correspondingField,omitempty"` + ModelID ID `json:"modelId"` + SchemaID ID `json:"schemaId"` + Schema *Schema `json:"schema"` + CorrespondingFieldID *ID `json:"correspondingFieldId,omitempty"` + CorrespondingField *SchemaField `json:"correspondingField,omitempty"` } func (SchemaFieldReference) IsSchemaFieldTypeProperty() {} type SchemaFieldReferenceInput struct { - ModelID ID `json:"modelId"` - CorrespondingSchemaID *ID `json:"correspondingSchemaId,omitempty"` - CorrespondingField *CorrespondingFieldInput `json:"correspondingField,omitempty"` + ModelID ID `json:"modelId"` + SchemaID ID `json:"schemaId"` + CorrespondingField *CorrespondingFieldInput `json:"correspondingField,omitempty"` } type SchemaFieldRichText struct { diff --git a/server/internal/adapter/gql/resolver_schema_field_reference.go b/server/internal/adapter/gql/resolver_schema_field_reference.go index 4a1f0bb9a5..6a2180f9ff 100644 --- a/server/internal/adapter/gql/resolver_schema_field_reference.go +++ b/server/internal/adapter/gql/resolver_schema_field_reference.go @@ -13,11 +13,15 @@ func (r *Resolver) SchemaFieldReference() SchemaFieldReferenceResolver { type schemaFieldReferenceResolver struct{ *Resolver } +func (s schemaFieldReferenceResolver) Schema(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.Schema, error) { + return dataloaders(ctx).Schema.Load(obj.SchemaID) +} + func (s schemaFieldReferenceResolver) CorrespondingField(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.SchemaField, error) { - if obj.CorrespondingFieldID == nil || obj.CorrespondingSchemaID == nil { + if obj.CorrespondingFieldID == nil { return nil, nil } - ss, err := dataloaders(ctx).Schema.Load(*obj.CorrespondingSchemaID) + ss, err := dataloaders(ctx).Schema.Load(obj.SchemaID) if err != nil { return nil, err } @@ -30,10 +34,3 @@ func (s schemaFieldReferenceResolver) CorrespondingField(ctx context.Context, ob return ff, nil } - -func (s schemaFieldReferenceResolver) CorrespondingSchema(ctx context.Context, obj *gqlmodel.SchemaFieldReference) (*gqlmodel.Schema, error) { - if obj.CorrespondingSchemaID == nil { - return nil, nil - } - return dataloaders(ctx).Schema.Load(*obj.CorrespondingSchemaID) -} diff --git a/server/internal/infrastructure/mongo/mongodoc/schema.go b/server/internal/infrastructure/mongo/mongodoc/schema.go index afcb0ff19f..8d7d17b181 100644 --- a/server/internal/infrastructure/mongo/mongodoc/schema.go +++ b/server/internal/infrastructure/mongo/mongodoc/schema.go @@ -77,9 +77,9 @@ type FieldIntegerPropertyDocument struct { } type FieldReferencePropertyDocument struct { - Model string - CorrespondingSchema *string - CorrespondingField *string + Model string + Schema string + CorrespondingField *string } type FieldGroupPropertyDocument struct { @@ -164,9 +164,9 @@ func NewSchema(s *schema.Schema) (*SchemaDocument, string) { }, Reference: func(fp *schema.FieldReference) { fd.TypeProperty.Reference = &FieldReferencePropertyDocument{ - Model: fp.Model().String(), - CorrespondingSchema: fp.CorrespondingSchema().StringRef(), - CorrespondingField: fp.CorrespondingFieldID().StringRef(), + Model: fp.Model().String(), + Schema: fp.Schema().String(), + CorrespondingField: fp.CorrespondingFieldID().StringRef(), } }, Group: func(fp *schema.FieldGroup) { @@ -268,15 +268,15 @@ func (d *SchemaDocument) Model() (*schema.Schema, error) { if err != nil { return nil, err } + sid, err := id.SchemaIDFrom(tpd.Reference.Schema) + if err != nil { + return nil, err + } var cfid *id.FieldID if tpd.Reference.CorrespondingField != nil { cfid = id.FieldIDFromRef(tpd.Reference.CorrespondingField) } - var sid *id.SchemaID - if tpd.Reference.CorrespondingSchema != nil { - sid = id.SchemaIDFromRef(tpd.Reference.CorrespondingSchema) - } - tp = schema.NewReference(mid, sid, nil, cfid).TypeProperty() + tp = schema.NewReference(mid, sid, cfid, nil).TypeProperty() case value.TypeURL: tp = schema.NewURL().TypeProperty() case value.TypeGroup: diff --git a/server/internal/usecase/interactor/item_test.go b/server/internal/usecase/interactor/item_test.go index a32c4ab94f..d98e28f4c9 100644 --- a/server/internal/usecase/interactor/item_test.go +++ b/server/internal/usecase/interactor/item_test.go @@ -590,33 +590,31 @@ func TestItem_IsItemReferenced(t *testing.T) { fid1 := id.NewFieldID() fid2 := id.NewFieldID() cf1 := &schema.CorrespondingField{ - FieldID: fid2.Ref(), - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), + Title: "title", + Key: "key", + Description: "description", + Required: true, } - sf1 := schema.NewField(schema.NewReference(id.NewModelID(), sid2.Ref(), cf1, cf1.FieldID).TypeProperty()).ID(fid1).Name("f").Unique(true).Key(key.Random()).MustBuild() + sf1 := schema.NewField(schema.NewReference(id.NewModelID(), sid2, fid2.Ref(), cf1).TypeProperty()).ID(fid1).Name("f").Unique(true).Key(key.Random()).MustBuild() s1 := schema.New().ID(sid1).Workspace(w).Project(prj.ID()).Fields(schema.FieldList{sf1}).MustBuild() m1 := model.New().NewID().Schema(s1.ID()).Key(key.Random()).Project(s1.Project()).MustBuild() fs1 := []*item.Field{item.NewField(sf1.ID(), value.TypeReference.Value(id.NewItemID()).AsMultiple(), nil)} i1 := item.New().NewID().Schema(s1.ID()).Model(m1.ID()).Project(s1.Project()).Thread(id.NewThreadID()).Fields(fs1).MustBuild() cf2 := &schema.CorrespondingField{ - FieldID: fid1.Ref(), - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), + Title: "title", + Key: "key", + Description: "description", + Required: true, } - sf2 := schema.NewField(schema.NewReference(id.NewModelID(), sid1.Ref(), cf2, cf2.FieldID).TypeProperty()).ID(fid2).Name("f").Unique(true).Key(key.Random()).MustBuild() + sf2 := schema.NewField(schema.NewReference(id.NewModelID(), sid1, fid1.Ref(), cf2).TypeProperty()).ID(fid2).Name("f").Unique(true).Key(key.Random()).MustBuild() s2 := schema.New().ID(sid2).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(schema.FieldList{sf2}).MustBuild() m2 := model.New().NewID().Schema(s2.ID()).Key(key.Random()).Project(s2.Project()).MustBuild() fs2 := []*item.Field{item.NewField(sf2.ID(), value.TypeReference.Value(id.NewItemID()).AsMultiple(), nil)} i2 := item.New().NewID().Schema(s2.ID()).Model(m2.ID()).Project(s2.Project()).Thread(id.NewThreadID()).Fields(fs2).MustBuild() fid3 := id.NewFieldID() - sf3 := schema.NewField(schema.NewReference(id.NewModelID(), nil, nil, nil).TypeProperty()).ID(fid3).Name("f").Unique(true).Key(key.Random()).MustBuild() + sf3 := schema.NewField(schema.NewReference(id.NewModelID(), id.NewSchemaID(), nil, nil).TypeProperty()).ID(fid3).Name("f").Unique(true).Key(key.Random()).MustBuild() s3 := schema.New().ID(sid2).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(schema.FieldList{sf3}).MustBuild() m3 := model.New().NewID().Schema(s3.ID()).Key(key.Random()).Project(s3.Project()).MustBuild() fs3 := []*item.Field{item.NewField(sf3.ID(), value.TypeReference.Value(nil).AsMultiple(), nil)} diff --git a/server/internal/usecase/interactor/schema.go b/server/internal/usecase/interactor/schema.go index 52d254de2b..a73b74c43a 100644 --- a/server/internal/usecase/interactor/schema.go +++ b/server/internal/usecase/interactor/schema.go @@ -12,8 +12,6 @@ import ( "github.com/reearth/reearth-cms/server/pkg/key" "github.com/reearth/reearth-cms/server/pkg/schema" "github.com/reearth/reearth-cms/server/pkg/value" - "github.com/reearth/reearthx/i18n" - "github.com/reearth/reearthx/rerror" "github.com/samber/lo" ) @@ -29,11 +27,11 @@ func NewSchema(r *repo.Container, g *gateway.Container) interfaces.Schema { } } -func (i Schema) FindByID(ctx context.Context, id id.SchemaID, operator *usecase.Operator) (*schema.Schema, error) { +func (i Schema) FindByID(ctx context.Context, id id.SchemaID, _ *usecase.Operator) (*schema.Schema, error) { return i.repos.Schema.FindByID(ctx, id) } -func (i Schema) FindByIDs(ctx context.Context, ids []id.SchemaID, operator *usecase.Operator) (schema.List, error) { +func (i Schema) FindByIDs(ctx context.Context, ids []id.SchemaID, _ *usecase.Operator) (schema.List, error) { return i.repos.Schema.FindByIDs(ctx, ids) } @@ -82,22 +80,22 @@ func (i Schema) FindByModel(ctx context.Context, mID id.ModelID, _ *usecase.Oper return schema.NewPackage(s, sList.Schema(m.Metadata()), gm), nil } -func (i Schema) CreateField(ctx context.Context, param interfaces.CreateFieldParam, operator *usecase.Operator) (*schema.Field, error) { - return Run1(ctx, operator, i.repos, Usecase().Transaction(), func(ctx context.Context) (*schema.Field, error) { - s1, err := i.repos.Schema.FindByID(ctx, param.SchemaID) +func (i Schema) CreateField(ctx context.Context, param interfaces.CreateFieldParam, op *usecase.Operator) (*schema.Field, error) { + return Run1(ctx, op, i.repos, Usecase().Transaction(), func(ctx context.Context) (*schema.Field, error) { + s, err := i.repos.Schema.FindByID(ctx, param.SchemaID) if err != nil { return nil, err } - if !operator.IsMaintainingProject(s1.Project()) { + if !op.IsMaintainingProject(s.Project()) { return nil, interfaces.ErrOperationDenied } - if param.Key == "" || s1.HasFieldByKey(param.Key) { + if param.Key == "" || s.HasFieldByKey(param.Key) { return nil, schema.ErrInvalidKey } - f1, err := schema.NewField(param.TypeProperty). + f, err := schema.NewField(param.TypeProperty). NewID(). Unique(param.Unique). Multiple(param.Multiple). @@ -112,7 +110,7 @@ func (i Schema) CreateField(ctx context.Context, param interfaces.CreateFieldPar } if param.Type == value.TypeReference { - err = i.createCorrespondingField(ctx, s1, f1, param, operator) + err = i.createCorrespondingField(ctx, s, f, param) if err != nil { return nil, err } @@ -131,84 +129,79 @@ func (i Schema) CreateField(ctx context.Context, param interfaces.CreateFieldPar } } - s1.AddField(f1) + s.AddField(f) - if err := setTitleField(¶m.IsTitle, s1, f1.ID().Ref()); err != nil { + if err := setTitleField(¶m.IsTitle, s, f.ID().Ref()); err != nil { return nil, err } - if err := i.repos.Schema.Save(ctx, s1); err != nil { + if err := i.repos.Schema.Save(ctx, s); err != nil { return nil, err } - return f1, nil + return f, nil }) } -func (i Schema) createCorrespondingField(ctx context.Context, s1 *schema.Schema, f1 *schema.Field, param interfaces.CreateFieldParam, operator *usecase.Operator) error { - fr, _ := schema.FieldReferenceFromTypeProperty(f1.TypeProperty()) - // check if reference direction is two way - if fr.CorrespondingField() != nil { - mid2 := fr.Model() - var s2 *schema.Schema - // check self reference - if param.ModelID != nil && mid2 == *param.ModelID { - s2 = s1 - } else { - m2, err := i.repos.Model.FindByID(ctx, mid2) - if err != nil { - return err - } - s2, err = i.repos.Schema.FindByID(ctx, m2.Schema()) - if err != nil { - return err - } - - if !operator.IsMaintainingProject(s2.Project()) { - return interfaces.ErrOperationDenied - } - } +func (i Schema) createCorrespondingField(ctx context.Context, s *schema.Schema, f *schema.Field, param interfaces.CreateFieldParam) error { + rInput, _ := schema.FieldReferenceFromTypeProperty(param.TypeProperty) + // if the corresponding field is not passed it's not two-way + if rInput.CorrespondingField() == nil { + return nil + } - fr.SetCorrespondingSchema(s2.ID().Ref()) - fields, err := schema.GetCorrespondingFields(s1, s2, *param.ModelID, f1, fr) + rs := s + if s.ID() != rInput.Schema() { + s, err := i.repos.Schema.FindByID(ctx, rInput.Schema()) if err != nil { return err } + rs = s + } - fields.Schema2.AddField(fields.Field2) + if rs.HasFieldByKey(rInput.CorrespondingField().Key) { + return interfaces.ErrReferencedFiledKeyExists + } - if err := i.repos.Schema.Save(ctx, fields.Schema2); err != nil { - return err - } + cf, err := schema.CreateCorrespondingField(s.ID(), *param.ModelID, f, *rInput.CorrespondingField()) + if err != nil { + return err } + + rs.AddField(cf) + + if err := i.repos.Schema.Save(ctx, rs); err != nil { + return err + } + return nil } -func (i Schema) UpdateField(ctx context.Context, param interfaces.UpdateFieldParam, operator *usecase.Operator) (*schema.Field, error) { - return Run1(ctx, operator, i.repos, Usecase().Transaction(), func(ctx context.Context) (*schema.Field, error) { - s1, err := i.repos.Schema.FindByID(ctx, param.SchemaID) +func (i Schema) UpdateField(ctx context.Context, param interfaces.UpdateFieldParam, op *usecase.Operator) (*schema.Field, error) { + return Run1(ctx, op, i.repos, Usecase().Transaction(), func(ctx context.Context) (*schema.Field, error) { + s, err := i.repos.Schema.FindByID(ctx, param.SchemaID) if err != nil { return nil, err } - if !operator.IsMaintainingProject(s1.Project()) { + if !op.IsMaintainingProject(s.Project()) { return nil, interfaces.ErrOperationDenied } - f1 := s1.Field(param.FieldID) - if f1 == nil { + f := s.Field(param.FieldID) + if f == nil { return nil, interfaces.ErrFieldNotFound } // check if type is reference - if f1.Type() == value.TypeReference { - err := i.updateCorrespondingField(ctx, s1, f1, param, operator) + if f.Type() == value.TypeReference { + err := i.updateCorrespondingField(ctx, s, f, param) if err != nil { return nil, err } } - if f1.Type() == value.TypeGroup { + if f.Type() == value.TypeGroup { var g *schema.FieldGroup param.TypeProperty.Match(schema.TypePropertyMatch{ Group: func(f *schema.FieldGroup) { @@ -221,19 +214,19 @@ func (i Schema) UpdateField(ctx context.Context, param interfaces.UpdateFieldPar } } - if err := updateField(param, f1); err != nil { + if err := updateField(param, f); err != nil { return nil, err } - if err := setTitleField(param.IsTitle, s1, f1.ID().Ref()); err != nil { + if err := setTitleField(param.IsTitle, s, f.ID().Ref()); err != nil { return nil, err } - if err := i.repos.Schema.Save(ctx, s1); err != nil { + if err := i.repos.Schema.Save(ctx, s); err != nil { return nil, err } - return f1, nil + return f, nil }) } @@ -257,88 +250,52 @@ func setTitleField(isTitle *bool, s *schema.Schema, fid *id.FieldID) error { return nil } -func (i Schema) updateCorrespondingField(ctx context.Context, s1 *schema.Schema, f1 *schema.Field, param interfaces.UpdateFieldParam, operator *usecase.Operator) error { - oldFr, _ := schema.FieldReferenceFromTypeProperty(f1.TypeProperty()) +func (i Schema) updateCorrespondingField(ctx context.Context, s *schema.Schema, f *schema.Field, param interfaces.UpdateFieldParam) error { + oldFr, _ := schema.FieldReferenceFromTypeProperty(f.TypeProperty()) newFr, _ := schema.FieldReferenceFromTypeProperty(param.TypeProperty) - // check if reference direction is two way - if newFr.CorrespondingField() != nil { - mid2 := oldFr.Model() - m2, err := i.repos.Model.FindByID(ctx, mid2) - if err != nil { - return err - } - var s2 *schema.Schema - // check self reference - if param.ModelID != nil && mid2 == *param.ModelID { - s2 = s1 - } else { - s2, err = i.repos.Schema.FindByID(ctx, m2.Schema()) - if err != nil { - return err - } - } - if !operator.IsMaintainingProject(s2.Project()) { - return interfaces.ErrOperationDenied - } - - // check if modelId is different - if oldFr.Model() == newFr.Model() { - // if modelId is same, update f2 - cf1 := newFr.CorrespondingField() - f2 := s2.Field(*cf1.FieldID) - if f2 == nil { - return interfaces.ErrFieldNotFound - } - if err := updateField(interfaces.UpdateFieldParam{ - ModelID: mid2.Ref(), - SchemaID: m2.Schema(), - FieldID: *cf1.FieldID, - Name: cf1.Title, - Description: cf1.Description, - Key: cf1.Key, - Required: cf1.Required, - }, f2); err != nil { - return err - } - if err := i.repos.Schema.Save(ctx, s2); err != nil { - return err - } - } else { - // delete the old corresponding field - s2.RemoveField(*oldFr.CorrespondingFieldID()) - if err := i.repos.Schema.Save(ctx, s2); err != nil { - return err - } - // create the new corresponding field - mid3 := newFr.Model() - m3, err := i.repos.Model.FindByID(ctx, mid3) - if err != nil { - return err - } - s3, err := i.repos.Schema.FindByID(ctx, m3.Schema()) - if err != nil { - return err - } + // check if reference direction is changed + if (oldFr.CorrespondingFieldID() == nil) != (newFr.CorrespondingField() == nil) { + return interfaces.ErrReferenceDirectionChanged + } - if !operator.IsMaintainingProject(s3.Project()) { - return interfaces.ErrOperationDenied - } + // if it's not two-way reference no need to update + if newFr.CorrespondingField() == nil { + return nil + } - fields, err := schema.GetCorrespondingFields(s1, s3, *param.ModelID, f1, newFr) - if err != nil { - return err - } - if err != nil { - return err - } + if oldFr.Model() != newFr.Model() || oldFr.Schema() != newFr.Schema() { + return interfaces.ErrReferenceModelChanged + } - fields.Schema2.AddField(fields.Field2) - if err := i.repos.Schema.Save(ctx, fields.Schema2); err != nil { - return err - } + rs := s + if s.ID() != newFr.Schema() { + s, err := i.repos.Schema.FindByID(ctx, newFr.Schema()) + if err != nil { + return err } + rs = s + } + + rf := rs.Field(*oldFr.CorrespondingFieldID()) + if rf == nil { + return interfaces.ErrFieldNotFound } + if err := updateField(interfaces.UpdateFieldParam{ + ModelID: newFr.Model().Ref(), + SchemaID: newFr.Schema(), + FieldID: *oldFr.CorrespondingFieldID(), + Name: &newFr.CorrespondingField().Title, + Description: &newFr.CorrespondingField().Description, + Key: &newFr.CorrespondingField().Key, + Required: &newFr.CorrespondingField().Required, + }, rf); err != nil { + return err + } + if err := i.repos.Schema.Save(ctx, rs); err != nil { + return err + } + return nil } @@ -359,7 +316,7 @@ func (i Schema) DeleteField(ctx context.Context, schemaId id.SchemaID, fieldID i return interfaces.ErrFieldNotFound } if f.Type() == value.TypeReference { - err := i.deleteCorrespondingField(ctx, s, f, operator) + err := i.deleteCorrespondingField(ctx, s, f) if err != nil { return err } @@ -370,34 +327,26 @@ func (i Schema) DeleteField(ctx context.Context, schemaId id.SchemaID, fieldID i }) } -func (i Schema) deleteCorrespondingField(ctx context.Context, s *schema.Schema, f *schema.Field, operator *usecase.Operator) error { +func (i Schema) deleteCorrespondingField(ctx context.Context, s *schema.Schema, f *schema.Field) error { fr, _ := schema.FieldReferenceFromTypeProperty(f.TypeProperty()) - cfid := fr.CorrespondingFieldID() - - // check if reference direction is two way - if cfid != nil { - ms, err := i.repos.Model.FindByIDs(ctx, []id.ModelID{fr.Model()}) - if err != nil || len(ms) != 1 { - if err == nil { - return rerror.NewE(i18n.T("not found")) - } - return err - } - m := ms[0] + if fr.CorrespondingFieldID() == nil { + return nil + } - s2, err := i.repos.Schema.FindByID(ctx, m.Schema()) + rs := s + if s.ID() != fr.Schema() { + s, err := i.repos.Schema.FindByID(ctx, fr.Schema()) if err != nil { return err } - // check self reference - if s2.ID() == s.ID() { - s2 = s - } - s2.RemoveField(*cfid) - if err := i.repos.Schema.Save(ctx, s2); err != nil { - return err - } + rs = s + } + + rs.RemoveField(*fr.CorrespondingFieldID()) + if err := i.repos.Schema.Save(ctx, rs); err != nil { + return err } + return nil } @@ -478,7 +427,7 @@ func updateField(param interfaces.UpdateFieldParam, f *schema.Field) error { return nil } -func (i Schema) GetSchemasAndGroupSchemasByIDs(ctx context.Context, list id.SchemaIDList, operator *usecase.Operator) (schemas schema.List, groupSchemas schema.List, err error) { +func (i Schema) GetSchemasAndGroupSchemasByIDs(ctx context.Context, list id.SchemaIDList, _ *usecase.Operator) (schemas schema.List, groupSchemas schema.List, err error) { schemas, err = i.repos.Schema.FindByIDs(ctx, list) if err != nil { return diff --git a/server/internal/usecase/interfaces/schema.go b/server/internal/usecase/interfaces/schema.go index 1559bfec17..4e29eb2d2d 100644 --- a/server/internal/usecase/interfaces/schema.go +++ b/server/internal/usecase/interfaces/schema.go @@ -43,10 +43,13 @@ type UpdateFieldParam struct { } var ( - ErrInvalidTypeProperty = rerror.NewE(i18n.T("invalid type property")) - ErrFieldNotFound = rerror.NewE(i18n.T("field not found")) - ErrInvalidValue = rerror.NewE(i18n.T("invalid value")) - ErrEitherModelOrGroup = rerror.NewE(i18n.T("either model or group should be provided")) + ErrInvalidTypeProperty = rerror.NewE(i18n.T("invalid type property")) + ErrReferencedFiledKeyExists = rerror.NewE(i18n.T("referenced field key exists")) + ErrReferenceDirectionChanged = rerror.NewE(i18n.T("reference field direction can not be changed")) + ErrReferenceModelChanged = rerror.NewE(i18n.T("reference field model can not be changed")) + ErrFieldNotFound = rerror.NewE(i18n.T("field not found")) + ErrInvalidValue = rerror.NewE(i18n.T("invalid value")) + ErrEitherModelOrGroup = rerror.NewE(i18n.T("either model or group should be provided")) ) type Schema interface { diff --git a/server/pkg/item/reference_test.go b/server/pkg/item/reference_test.go index 572cfe3ef4..8a888f0260 100644 --- a/server/pkg/item/reference_test.go +++ b/server/pkg/item/reference_test.go @@ -9,7 +9,6 @@ import ( "github.com/reearth/reearth-cms/server/pkg/schema" "github.com/reearth/reearth-cms/server/pkg/value" "github.com/reearth/reearthx/account/accountdomain" - "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -19,22 +18,20 @@ func Test_AreItemsReferenced(t *testing.T) { fid1, fid2, fid3 := id.NewFieldID(), id.NewFieldID(), id.NewFieldID() sid1, sid2, sid3 := id.NewSchemaID(), id.NewSchemaID(), id.NewSchemaID() cf1 := &schema.CorrespondingField{ - FieldID: fid2.Ref(), - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), + Title: "title", + Key: "key", + Description: "description", + Required: true, } cf2 := &schema.CorrespondingField{ - FieldID: fid1.Ref(), - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), + Title: "title", + Key: "key", + Description: "description", + Required: true, } - f1 := schema.NewField(schema.NewReference(mid2, sid1.Ref(), cf1, cf1.FieldID).TypeProperty()).ID(fid1).Key(key.Random()).MustBuild() - f2 := schema.NewField(schema.NewReference(mid1, sid2.Ref(), cf2, cf2.FieldID).TypeProperty()).ID(fid2).Key(key.Random()).MustBuild() - f3 := schema.NewField(schema.NewReference(mid3, nil, nil, nil).TypeProperty()).ID(fid3).Key(key.Random()).MustBuild() + f1 := schema.NewField(schema.NewReference(mid2, sid1, fid2.Ref(), cf1).TypeProperty()).ID(fid1).Key(key.Random()).MustBuild() + f2 := schema.NewField(schema.NewReference(mid1, sid2, fid1.Ref(), cf2).TypeProperty()).ID(fid2).Key(key.Random()).MustBuild() + f3 := schema.NewField(schema.NewReference(mid3, sid3, nil, nil).TypeProperty()).ID(fid3).Key(key.Random()).MustBuild() s1 := schema.New().ID(sid1).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(schema.FieldList{f1}).MustBuild() s2 := schema.New().ID(sid2).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(schema.FieldList{f2}).MustBuild() s3 := schema.New().ID(sid3).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(schema.FieldList{f3}).MustBuild() diff --git a/server/pkg/schema/corresponding_field.go b/server/pkg/schema/corresponding_field.go index 26644d7347..e24d80f2d6 100644 --- a/server/pkg/schema/corresponding_field.go +++ b/server/pkg/schema/corresponding_field.go @@ -4,70 +4,37 @@ import ( "github.com/reearth/reearth-cms/server/pkg/id" "github.com/reearth/reearth-cms/server/pkg/key" "github.com/reearth/reearth-cms/server/pkg/value" - "github.com/samber/lo" ) -type CorrespondingFieldTuple struct { - Schema1 *Schema - Field1 *Field - Schema2 *Schema - Field2 *Field -} - -func GetCorrespondingFields(s1, s2 *Schema, mid id.ModelID, f1 *Field, fr *FieldReference) (*CorrespondingFieldTuple, error) { - // check if reference direction is two way - if cf1 := fr.CorrespondingField(); cf1 != nil { - if cf1.Key == nil || s2.HasFieldByKey(lo.FromPtr(cf1.Key)) { - return nil, ErrInvalidKey - } - - cf2 := &CorrespondingField{ - FieldID: f1.ID().Ref(), - Title: lo.ToPtr(f1.Name()), - Key: lo.ToPtr(f1.Key().String()), - Description: lo.ToPtr(f1.Description()), - Required: lo.ToPtr(f1.Required()), - } - tp := NewReference(mid, s1.ID().Ref(), cf2, cf2.FieldID).TypeProperty() +func CreateCorrespondingField(sid id.SchemaID, mid id.ModelID, f *Field, inp CorrespondingField) (*Field, error) { + if f == nil || f.typeProperty == nil || f.typeProperty.reference == nil { + return nil, ErrInvalidType + } - f2, err := NewField(tp). - NewID(). - Unique(false). - Multiple(false). - Required(lo.FromPtr(cf1.Required)). - Name(lo.FromPtr(cf1.Title)). - Description(lo.FromPtr(cf1.Description)). - Key(key.New(lo.FromPtr(cf1.Key))). - DefaultValue(nil). - Build() - if err != nil { - return nil, err - } + tp := NewReference(mid, sid, f.ID().Ref(), nil).TypeProperty() + + cf, err := NewField(tp). + NewID(). + Unique(false). + Multiple(false). + Required(inp.Required). + Name(inp.Title). + Description(inp.Description). + Key(key.New(inp.Key)). + DefaultValue(nil). + Build() + if err != nil { + return nil, err + } - fr.SetCorrespondingField(f2.ID().Ref()) + f.typeProperty.reference.correspondingFieldID = cf.ID().Ref() - return &CorrespondingFieldTuple{ - Schema1: s1, - Field1: f1, - Schema2: s2, - Field2: f2, - }, nil - } - return nil, nil + return cf, nil } func FieldReferenceFromTypeProperty(tp *TypeProperty) (*FieldReference, bool) { - if tp.Type() != value.TypeReference { - return nil, false - } - var fr *FieldReference - tp.Match(TypePropertyMatch{ - Reference: func(f *FieldReference) { - fr = f - }, - }) - if fr == nil { + if tp == nil { return nil, false } - return fr, true + return tp.reference, tp.Type() == value.TypeReference && tp.reference != nil } diff --git a/server/pkg/schema/corresponding_field_test.go b/server/pkg/schema/corresponding_field_test.go index 8b103fbe57..e9eb5a5c75 100644 --- a/server/pkg/schema/corresponding_field_test.go +++ b/server/pkg/schema/corresponding_field_test.go @@ -5,99 +5,21 @@ import ( "github.com/reearth/reearth-cms/server/pkg/id" "github.com/reearth/reearth-cms/server/pkg/key" - "github.com/reearth/reearth-cms/server/pkg/project" - "github.com/reearth/reearthx/account/accountdomain" - "github.com/samber/lo" "github.com/stretchr/testify/assert" ) -func TestGetCorrespondingFields(t *testing.T) { - prj := project.New().NewID().MustBuild() - mid1 := id.NewModelID() - mid2 := id.NewModelID() - fid1 := id.NewFieldID() - sid1 := id.NewSchemaID() - sid2 := id.NewSchemaID() - cf1 := &CorrespondingField{ - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), - } - f1 := NewField(NewReference(mid2, sid1.Ref(), cf1, cf1.FieldID).TypeProperty()).ID(fid1).Key(key.Random()).MustBuild() - s1 := New().ID(sid1).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(FieldList{f1}).MustBuild() - s2 := New().ID(sid2).Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(FieldList{}).MustBuild() - fr1, _ := FieldReferenceFromTypeProperty(f1.TypeProperty()) - fields, _ := GetCorrespondingFields(s1, s2, mid1, f1, fr1) - fr1.SetCorrespondingSchema(sid2.Ref()) - - // check that fields are not nil - assert.NotNil(t, fields.Schema1) - assert.NotNil(t, fields.Schema2) - assert.NotNil(t, fields.Field1) - assert.NotNil(t, fields.Field2) - - // check that model ids are correct - rf1, _ := FieldReferenceFromTypeProperty(fields.Field2.TypeProperty()) - assert.Equal(t, rf1.modelID, mid1) - rf2, _ := FieldReferenceFromTypeProperty(fields.Field1.TypeProperty()) - assert.Equal(t, rf2.modelID, mid2) - - // check that corresponding field ids are correct - assert.Equal(t, fields.Field1.typeProperty.reference.correspondingFieldID, fields.Field2.ID().Ref()) - assert.Equal(t, fields.Field2.typeProperty.reference.correspondingFieldID, fields.Field1.ID().Ref()) - - assert.Equal(t, fields.Field1.typeProperty.reference.correspondingSchemaID, fields.Schema2.ID().Ref()) - assert.Equal(t, fields.Field2.typeProperty.reference.correspondingSchemaID, fields.Schema1.ID().Ref()) - // check that corresponding field is set correctly - wantcf2 := &CorrespondingField{ - FieldID: f1.ID().Ref(), - Title: lo.ToPtr(f1.Name()), - Key: lo.ToPtr(f1.Key().String()), - Description: lo.ToPtr(f1.Description()), - Required: lo.ToPtr(f1.Required()), - } - fr2, ok := FieldReferenceFromTypeProperty(fields.Field2.TypeProperty()) - assert.True(t, ok) - assert.Equal(t, wantcf2, fr2.correspondingField) - - // check invalid key - cf3 := &CorrespondingField{ - Title: lo.ToPtr("title"), - Key: nil, - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), - } - f3 := NewField(NewReference(mid2, id.NewSchemaID().Ref(), cf3, cf3.FieldID).TypeProperty()).NewID().Key(key.Random()).MustBuild() - s3 := New().NewID().Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(FieldList{f3}).MustBuild() - s4 := New().NewID().Workspace(accountdomain.NewWorkspaceID()).Project(prj.ID()).Fields(FieldList{}).MustBuild() - - mid3 := id.NewModelID() - fr3, _ := FieldReferenceFromTypeProperty(f3.TypeProperty()) - fields, err := GetCorrespondingFields(s3, s4, mid3, f3, fr3) - assert.Nil(t, fields) - assert.Equal(t, err, ErrInvalidKey) - - // check one way reference - mid4 := id.NewModelID() - f4 := NewField(NewReference(mid2, nil, nil, nil).TypeProperty()).NewID().Key(key.Random()).MustBuild() - fr4, _ := FieldReferenceFromTypeProperty(f4.TypeProperty()) - fields, err = GetCorrespondingFields(s3, s4, mid4, f4, fr4) - assert.Nil(t, fields) - assert.Nil(t, err) -} - func TestFieldReferenceFromTypeProperty(t *testing.T) { // check that it returns true and correct field reference if type is reference mid1 := id.NewModelID() + sid1 := id.NewSchemaID() fid1 := id.NewFieldID() - f1 := NewField(NewReference(mid1, nil, nil, nil).TypeProperty()).ID(fid1).Key(key.Random()).MustBuild() + f1 := NewField(NewReference(mid1, sid1, nil, nil).TypeProperty()).ID(fid1).Key(key.Random()).MustBuild() got1, ok := FieldReferenceFromTypeProperty(f1.TypeProperty()) want1 := &FieldReference{ - modelID: mid1, - correspondingSchemaID: nil, - correspondingFieldID: nil, - correspondingField: nil, + modelID: mid1, + schemaID: sid1, + correspondingFieldID: nil, + correspondingField: nil, } assert.True(t, ok) assert.Equal(t, want1, got1) diff --git a/server/pkg/schema/field_reference.go b/server/pkg/schema/field_reference.go index b7244dcf5b..8a2800ac73 100644 --- a/server/pkg/schema/field_reference.go +++ b/server/pkg/schema/field_reference.go @@ -7,26 +7,25 @@ import ( ) type CorrespondingField struct { - FieldID *id.FieldID - Title *string - Key *string - Description *string - Required *bool + Title string + Key string + Description string + Required bool } type FieldReference struct { - modelID id.ModelID - correspondingSchemaID *id.SchemaID - correspondingFieldID *id.FieldID - correspondingField *CorrespondingField + modelID id.ModelID + schemaID id.SchemaID + correspondingFieldID *id.FieldID + correspondingField *CorrespondingField // from user input only } -func NewReference(id id.ModelID, sid *id.SchemaID, cf *CorrespondingField, cfId *id.FieldID) *FieldReference { +func NewReference(mID id.ModelID, sID id.SchemaID, cfID *id.FieldID, cf *CorrespondingField) *FieldReference { return &FieldReference{ - modelID: id, - correspondingSchemaID: sid, - correspondingFieldID: cfId, - correspondingField: cf, + modelID: mID, + schemaID: sID, + correspondingFieldID: cfID, + correspondingField: cf, } } @@ -37,22 +36,16 @@ func (f *FieldReference) TypeProperty() *TypeProperty { } } -func (f *FieldReference) SetCorrespondingField(cf *id.FieldID) { - f.correspondingFieldID = cf -} - -func (f *FieldReference) SetCorrespondingSchema(sid *id.SchemaID) { - f.correspondingSchemaID = sid -} - func (f *FieldReference) Model() model.ID { return f.modelID } -func (f *FieldReference) CorrespondingSchema() *id.SchemaID { - return f.correspondingSchemaID +func (f *FieldReference) Schema() id.SchemaID { + return f.schemaID } +// CorrespondingField returns the corresponding field of this reference from user input. +// This is not stored in the database. func (f *FieldReference) CorrespondingField() *CorrespondingField { return f.correspondingField } @@ -70,10 +63,10 @@ func (f *FieldReference) Clone() *FieldReference { return nil } return &FieldReference{ - modelID: f.modelID, - correspondingSchemaID: f.correspondingSchemaID, - correspondingFieldID: f.correspondingFieldID, - correspondingField: f.correspondingField, + modelID: f.modelID, + schemaID: f.schemaID, + correspondingFieldID: f.correspondingFieldID, + correspondingField: f.correspondingField, } } @@ -92,6 +85,6 @@ func (f *FieldReference) Validate(v *value.Value) (err error) { return } -func (f *FieldReference) ValidateMultiple(v *value.Multiple) error { +func (f *FieldReference) ValidateMultiple(_ *value.Multiple) error { return nil } diff --git a/server/pkg/schema/field_reference_test.go b/server/pkg/schema/field_reference_test.go index 5d2824045a..1be92b0b48 100644 --- a/server/pkg/schema/field_reference_test.go +++ b/server/pkg/schema/field_reference_test.go @@ -5,59 +5,36 @@ import ( "github.com/reearth/reearth-cms/server/pkg/id" "github.com/reearth/reearth-cms/server/pkg/value" - "github.com/samber/lo" "github.com/stretchr/testify/assert" ) func TestNewReference(t *testing.T) { m := id.NewModelID() - cf := id.NewFieldID().Ref() - sid := id.NewSchemaID().Ref() - assert.Equal(t, &FieldReference{modelID: m, correspondingSchemaID: sid, correspondingFieldID: cf}, NewReference(m, sid, nil, cf)) -} - -func TestFieldReference_SetCorrespondingField(t *testing.T) { - m := id.NewModelID() - cf := id.NewFieldID().Ref() - sid := id.NewSchemaID().Ref() - f := NewReference(m, nil, nil, nil) - f.SetCorrespondingField(cf) - assert.Equal(t, &FieldReference{modelID: m, correspondingSchemaID: sid, correspondingFieldID: cf}, NewReference(m, sid, nil, cf)) -} - -func TestFieldReference_SetCorrespondingSchema(t *testing.T) { - m := id.NewModelID() - f := id.NewFieldID() sid := id.NewSchemaID() - cf := &CorrespondingField{ - Title: lo.ToPtr("title"), - Key: lo.ToPtr("key"), - Description: lo.ToPtr("description"), - Required: lo.ToPtr(true), - } - fr := NewReference(m, sid.Ref(), cf, f.Ref()) - fr.SetCorrespondingSchema(sid.Ref()) - assert.Equal(t, fr.correspondingSchemaID.Ref(), sid.Ref()) + cf := id.NewFieldID().Ref() + assert.Equal(t, &FieldReference{modelID: m, schemaID: sid, correspondingFieldID: cf}, NewReference(m, sid, cf, nil)) } func TestFieldReference_CorrespondingField(t *testing.T) { m := id.NewModelID() + sid := id.NewSchemaID() cf := &CorrespondingField{} - sid := id.NewSchemaID().Ref() - f := NewReference(m, sid, cf, nil) + f := NewReference(m, sid, id.NewFieldID().Ref(), cf) assert.Equal(t, cf, f.CorrespondingField()) } func TestFieldReference_CorrespondingFieldID(t *testing.T) { m := id.NewModelID() + s := id.NewSchemaID() cf := id.NewFieldID().Ref() - f := NewReference(m, nil, nil, cf) + f := NewReference(m, s, cf, nil) assert.Equal(t, cf, f.CorrespondingFieldID()) } func TestFieldReference_Model(t *testing.T) { m := id.NewModelID() - f := NewReference(m, nil, nil, nil) + s := id.NewSchemaID() + f := NewReference(m, s, nil, nil) assert.Equal(t, m, f.Model()) } diff --git a/server/pkg/schema/type_property_test.go b/server/pkg/schema/type_property_test.go index 1082198bbd..bf8cb9210b 100644 --- a/server/pkg/schema/type_property_test.go +++ b/server/pkg/schema/type_property_test.go @@ -450,7 +450,7 @@ func TestTypeProperty_Validate(t *testing.T) { { name: "Reference", args: args{ - tp: &TypeProperty{t: value.TypeReference, reference: NewReference(id.NewModelID(), nil, nil, nil)}, + tp: &TypeProperty{t: value.TypeReference, reference: NewReference(id.NewModelID(), id.NewSchemaID(), nil, nil)}, value: value.TypeReference.Value(id.NewItemID()), }, want: nil, diff --git a/server/schemas/field.graphql b/server/schemas/field.graphql index 61a4c69009..a5eca1822c 100644 --- a/server/schemas/field.graphql +++ b/server/schemas/field.graphql @@ -124,8 +124,8 @@ type SchemaFieldInteger { type SchemaFieldReference { modelId: ID! - correspondingSchemaId: ID - correspondingSchema: Schema + schemaId: ID! + schema: Schema! correspondingFieldId: ID correspondingField: SchemaField } @@ -204,15 +204,15 @@ input SchemaFieldIntegerInput { input CorrespondingFieldInput { fieldId: ID - title: String - key: String - description: String - required: Boolean + title: String! + key: String! + description: String! + required: Boolean! } input SchemaFieldReferenceInput { modelId: ID! - correspondingSchemaId: ID + schemaId: ID! correspondingField: CorrespondingFieldInput } diff --git a/web/src/components/molecules/Common/Form/GroupItem/index.tsx b/web/src/components/molecules/Common/Form/GroupItem/index.tsx index c6aebebc97..31e67aa81d 100644 --- a/web/src/components/molecules/Common/Form/GroupItem/index.tsx +++ b/web/src/components/molecules/Common/Form/GroupItem/index.tsx @@ -44,7 +44,7 @@ type Props = { linkItemModalPage: number; linkItemModalPageSize: number; onSearchTerm: (term?: string) => void; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload: () => void; onLinkItemTableChange: (page: number, pageSize: number) => void; onAssetTableChange: ( diff --git a/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx b/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx index 478f66e1ec..70475afb76 100644 --- a/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx +++ b/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx @@ -46,7 +46,7 @@ type Props = { linkItemModalPage: number; linkItemModalPageSize: number; onSearchTerm: (term?: string) => void; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload: () => void; onLinkItemTableChange: (page: number, pageSize: number) => void; onAssetTableChange: ( diff --git a/web/src/components/molecules/Content/Details/index.tsx b/web/src/components/molecules/Content/Details/index.tsx index 822f30e380..1bb22c79bd 100644 --- a/web/src/components/molecules/Content/Details/index.tsx +++ b/web/src/components/molecules/Content/Details/index.tsx @@ -53,7 +53,7 @@ export type Props = { linkItemModalTotalCount: number; linkItemModalPage: number; linkItemModalPageSize: number; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onSearchTerm: (term?: string) => void; onLinkItemTableChange: (page: number, pageSize: number) => void; onUnpublish: (itemIds: string[]) => Promise; diff --git a/web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx b/web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx index 617a9a60e5..79dc5663d1 100644 --- a/web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx +++ b/web/src/components/molecules/Content/Form/ReferenceFormItem/index.tsx @@ -17,12 +17,13 @@ type Props = { disabled?: boolean; correspondingFieldId: string; modelId?: string; + titleFieldId?: string | null; formItemsData?: FormItem[]; linkItemModalTitle?: string; linkItemModalTotalCount?: number; linkItemModalPage?: number; linkItemModalPageSize?: number; - onReferenceModelUpdate?: (modelId?: string) => void; + onReferenceModelUpdate?: (modelId: string, referenceFieldId: string) => void; onSearchTerm?: (term?: string) => void; onLinkItemTableReload?: () => void; onLinkItemTableChange?: (page: number, pageSize: number) => void; @@ -36,6 +37,7 @@ const ReferenceFormItem: React.FC = ({ correspondingFieldId, onChange, modelId, + titleFieldId, formItemsData, linkItemModalTitle, linkItemModalTotalCount, @@ -53,11 +55,10 @@ const ReferenceFormItem: React.FC = ({ const [currentItem, setCurrentItem] = useState(); const handleClick = useCallback(() => { - if (!onReferenceModelUpdate) return; - onReferenceModelUpdate(modelId); + if (!onReferenceModelUpdate || !modelId) return; + onReferenceModelUpdate(modelId, titleFieldId ?? ""); setVisible(true); - onSearchTerm?.(""); - }, [setVisible, onReferenceModelUpdate, modelId, onSearchTerm]); + }, [onReferenceModelUpdate, modelId, titleFieldId]); const handleLinkItemModalCancel = useCallback(() => { if (disabled) return; diff --git a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx index 7b43e1cff0..a479d2a544 100644 --- a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx +++ b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx @@ -34,7 +34,7 @@ interface GroupFieldProps { linkItemModalPage: number; linkItemModalPageSize: number; onSearchTerm: (term?: string) => void; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload: () => void; onLinkItemTableChange: (page: number, pageSize: number) => void; onAssetTableChange: ( diff --git a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx index eca5bae4e9..8ac22e55bc 100644 --- a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx +++ b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx @@ -13,7 +13,7 @@ interface ReferenceFieldProps { linkItemModalTotalCount: number; linkItemModalPage: number; linkItemModalPageSize: number; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onSearchTerm: (term?: string) => void; onLinkItemTableReload: () => void; onLinkItemTableChange: (page: number, pageSize: number) => void; @@ -43,6 +43,7 @@ const ReferenceField: React.FC = ({ correspondingFieldId={field.id} formItemsData={formItemsData} modelId={field.typeProperty?.modelId} + titleFieldId={field.typeProperty?.schema?.titleFieldId} onReferenceModelUpdate={onReferenceModelUpdate} linkItemModalTitle={linkItemModalTitle} linkedItemsModalList={linkedItemsModalList} diff --git a/web/src/components/molecules/Content/Form/index.tsx b/web/src/components/molecules/Content/Form/index.tsx index 392e938993..6195ee47d8 100644 --- a/web/src/components/molecules/Content/Form/index.tsx +++ b/web/src/components/molecules/Content/Form/index.tsx @@ -71,7 +71,7 @@ export interface Props { linkItemModalTotalCount: number; linkItemModalPage: number; linkItemModalPageSize: number; - onReferenceModelUpdate: (modelId?: string) => void; + onReferenceModelUpdate: (modelId: string, referenceFieldId: string) => void; onSearchTerm: (term?: string) => void; onLinkItemTableReload: () => void; onLinkItemTableChange: (page: number, pageSize: number) => void; diff --git a/web/src/components/molecules/Schema/FieldModal/FieldCreationModalWithSteps/index.tsx b/web/src/components/molecules/Schema/FieldModal/FieldCreationModalWithSteps/index.tsx index 4867f89389..98cc6c784b 100644 --- a/web/src/components/molecules/Schema/FieldModal/FieldCreationModalWithSteps/index.tsx +++ b/web/src/components/molecules/Schema/FieldModal/FieldCreationModalWithSteps/index.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState, useRef } from "react"; import Button from "@reearth-cms/components/atoms/Button"; import Checkbox from "@reearth-cms/components/atoms/Checkbox"; @@ -51,6 +51,7 @@ const FieldCreationModalWithSteps: React.FC = ({ }) => { const t = useT(); const [selectedModel, setSelectedModel] = useState(); + const schemaIdRef = useRef(); const [modelForm] = Form.useForm(); const [field1Form] = Form.useForm(); const [field2Form] = Form.useForm(); @@ -91,6 +92,7 @@ const FieldCreationModalWithSteps: React.FC = ({ typeProperty: { reference: { modelId: "", + schemaId: "", correspondingField: null, }, }, @@ -111,8 +113,9 @@ const FieldCreationModalWithSteps: React.FC = ({ ); const handleSelectModel = useCallback( - (modelId: string) => { + (modelId: string, option: { schemaId: string }) => { setSelectedModel(modelId); + schemaIdRef.current = option.schemaId; }, [setSelectedModel], ); @@ -133,6 +136,7 @@ const FieldCreationModalWithSteps: React.FC = ({ values.typeProperty = { reference: { modelId: selectedModel, + schemaId: schemaIdRef.current, correspondingField: null, }, }; @@ -165,6 +169,7 @@ const FieldCreationModalWithSteps: React.FC = ({ field1FormValues.typeProperty = { reference: { modelId: selectedModel ?? "", + schemaId: schemaIdRef.current ?? "", correspondingField: { ...fields2Values, fieldId: selectedField?.typeProperty?.correspondingField.id, @@ -179,6 +184,7 @@ const FieldCreationModalWithSteps: React.FC = ({ field1FormValues.typeProperty = { reference: { modelId: selectedModel ?? "", + schemaId: schemaIdRef.current ?? "", correspondingField: { ...fields2Values, }, @@ -255,9 +261,9 @@ const FieldCreationModalWithSteps: React.FC = ({ name="model" label={t("Select the model to reference")} rules={[{ required: true, message: t("Please select the model!") }]}> - {models?.map(model => ( - + {model.name}{" "} #{model.key} diff --git a/web/src/components/molecules/Schema/types.ts b/web/src/components/molecules/Schema/types.ts index 47bcb2668f..df77671a74 100644 --- a/web/src/components/molecules/Schema/types.ts +++ b/web/src/components/molecules/Schema/types.ts @@ -54,6 +54,7 @@ export type TypeProperty = { groupId?: string; tags?: Tag[]; values?: string[]; + schema?: { titleFieldId: string | null }; }; export type FieldTypePropertyInput = { @@ -68,6 +69,7 @@ export type FieldTypePropertyInput = { url?: { defaultValue: string }; reference?: { modelId: string; + schemaId: string; correspondingField: { key: string; title: string; diff --git a/web/src/components/organisms/Project/Content/ContentDetails/hooks.ts b/web/src/components/organisms/Project/Content/ContentDetails/hooks.ts index d6057da152..10f00c095e 100644 --- a/web/src/components/organisms/Project/Content/ContentDetails/hooks.ts +++ b/web/src/components/organisms/Project/Content/ContentDetails/hooks.ts @@ -130,14 +130,10 @@ export default () => { skip: !model?.id, }); - const handleSearchTerm = useCallback( - (term?: string) => { - titleId.current = itemsData?.searchItem.nodes[0]?.fields[1]?.schemaFieldId ?? titleId.current; - setSearchTerm(term ?? ""); - setLinkItemModalPage(1); - }, - [itemsData?.searchItem.nodes], - ); + const handleSearchTerm = useCallback((term?: string) => { + setSearchTerm(term ?? ""); + setLinkItemModalPage(1); + }, []); const handleLinkItemTableReload = useCallback(() => { refetch(); @@ -504,9 +500,14 @@ export default () => { const handleModalOpen = useCallback(() => setRequestModalShown(true), []); - const handleReferenceModelUpdate = useCallback((modelId?: string) => { - setReferenceModelId(modelId); - }, []); + const handleReferenceModelUpdate = useCallback( + (modelId: string, titleFieldId: string) => { + setReferenceModelId(modelId); + titleId.current = titleFieldId; + handleSearchTerm(); + }, + [handleSearchTerm], + ); return { linkedItemsModalList, diff --git a/web/src/components/organisms/Project/Content/ContentList/hooks.ts b/web/src/components/organisms/Project/Content/ContentList/hooks.ts index 8e27d83ef0..e38199a2e5 100644 --- a/web/src/components/organisms/Project/Content/ContentList/hooks.ts +++ b/web/src/components/organisms/Project/Content/ContentList/hooks.ts @@ -219,7 +219,7 @@ export default () => { } } else { if (field.type === "Reference") { - return item.referencedItems?.find(ref => ref.id === field.value)?.title ?? ""; + return item.referencedItems?.find(ref => ref.id === field.value)?.title ?? field.value; } else { if (Array.isArray(field.value) && field.value.length > 0) { return field.value.map(v => "" + v); diff --git a/web/src/gql/graphql-client-api.tsx b/web/src/gql/graphql-client-api.tsx index 4df4a82852..a84e9ce1df 100644 --- a/web/src/gql/graphql-client-api.tsx +++ b/web/src/gql/graphql-client-api.tsx @@ -221,11 +221,11 @@ export type ConditionInput = { }; export type CorrespondingFieldInput = { - description?: InputMaybe; + description: Scalars['String']; fieldId?: InputMaybe; - key?: InputMaybe; - required?: InputMaybe; - title?: InputMaybe; + key: Scalars['String']; + required: Scalars['Boolean']; + title: Scalars['String']; }; export type CreateAssetInput = { @@ -1578,15 +1578,15 @@ export type SchemaFieldReference = { __typename?: 'SchemaFieldReference'; correspondingField?: Maybe; correspondingFieldId?: Maybe; - correspondingSchema?: Maybe; - correspondingSchemaId?: Maybe; modelId: Scalars['ID']; + schema: Schema; + schemaId: Scalars['ID']; }; export type SchemaFieldReferenceInput = { correspondingField?: InputMaybe; - correspondingSchemaId?: InputMaybe; modelId: Scalars['ID']; + schemaId: Scalars['ID']; }; export type SchemaFieldRichText = { @@ -2466,7 +2466,7 @@ export type GetModelsQueryVariables = Exact<{ }>; -export type GetModelsQuery = { __typename?: 'Query', models: { __typename?: 'ModelConnection', nodes: Array<{ __typename?: 'Model', id: string, name: string, description: string, key: string, public: boolean, order?: number | null, metadataSchema?: { __typename?: 'Schema', id: string, fields: Array<{ __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, isTitle: boolean, multiple: boolean, order?: number | null, typeProperty?: { __typename?: 'SchemaFieldAsset', assetDefaultValue?: any | null } | { __typename?: 'SchemaFieldBool', defaultValue?: any | null } | { __typename?: 'SchemaFieldCheckbox', defaultValue?: any | null } | { __typename?: 'SchemaFieldDate', defaultValue?: any | null } | { __typename?: 'SchemaFieldGroup' } | { __typename?: 'SchemaFieldInteger', min?: number | null, max?: number | null, integerDefaultValue?: any | null } | { __typename?: 'SchemaFieldMarkdown', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldReference', modelId: string, correspondingField?: { __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, multiple: boolean, order?: number | null } | null } | { __typename?: 'SchemaFieldRichText' } | { __typename?: 'SchemaFieldSelect', values: Array, selectDefaultValue?: any | null } | { __typename?: 'SchemaFieldTag', selectDefaultValue?: any | null, tags: Array<{ __typename?: 'SchemaFieldTagValue', id: string, name: string, color: SchemaFieldTagColor }> } | { __typename?: 'SchemaFieldText', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldTextArea', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldURL', defaultValue?: any | null } | null }> } | null, schema: { __typename?: 'Schema', id: string, fields: Array<{ __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, isTitle: boolean, multiple: boolean, order?: number | null, typeProperty?: { __typename?: 'SchemaFieldAsset', assetDefaultValue?: any | null } | { __typename?: 'SchemaFieldBool', defaultValue?: any | null } | { __typename?: 'SchemaFieldCheckbox', defaultValue?: any | null } | { __typename?: 'SchemaFieldDate', defaultValue?: any | null } | { __typename?: 'SchemaFieldGroup', groupId: string } | { __typename?: 'SchemaFieldInteger', min?: number | null, max?: number | null, integerDefaultValue?: any | null } | { __typename?: 'SchemaFieldMarkdown', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldReference', modelId: string, correspondingField?: { __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, multiple: boolean, order?: number | null } | null } | { __typename?: 'SchemaFieldRichText' } | { __typename?: 'SchemaFieldSelect', values: Array, selectDefaultValue?: any | null } | { __typename?: 'SchemaFieldTag', selectDefaultValue?: any | null, tags: Array<{ __typename?: 'SchemaFieldTagValue', id: string, name: string, color: SchemaFieldTagColor }> } | { __typename?: 'SchemaFieldText', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldTextArea', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldURL', defaultValue?: any | null } | null }> } } | null> } }; +export type GetModelsQuery = { __typename?: 'Query', models: { __typename?: 'ModelConnection', nodes: Array<{ __typename?: 'Model', id: string, name: string, description: string, key: string, public: boolean, order?: number | null, metadataSchema?: { __typename?: 'Schema', id: string, fields: Array<{ __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, isTitle: boolean, multiple: boolean, order?: number | null, typeProperty?: { __typename?: 'SchemaFieldAsset', assetDefaultValue?: any | null } | { __typename?: 'SchemaFieldBool', defaultValue?: any | null } | { __typename?: 'SchemaFieldCheckbox', defaultValue?: any | null } | { __typename?: 'SchemaFieldDate', defaultValue?: any | null } | { __typename?: 'SchemaFieldGroup' } | { __typename?: 'SchemaFieldInteger', min?: number | null, max?: number | null, integerDefaultValue?: any | null } | { __typename?: 'SchemaFieldMarkdown', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldReference', modelId: string, correspondingField?: { __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, multiple: boolean, order?: number | null } | null } | { __typename?: 'SchemaFieldRichText' } | { __typename?: 'SchemaFieldSelect', values: Array, selectDefaultValue?: any | null } | { __typename?: 'SchemaFieldTag', selectDefaultValue?: any | null, tags: Array<{ __typename?: 'SchemaFieldTagValue', id: string, name: string, color: SchemaFieldTagColor }> } | { __typename?: 'SchemaFieldText', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldTextArea', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldURL', defaultValue?: any | null } | null }> } | null, schema: { __typename?: 'Schema', id: string, fields: Array<{ __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, isTitle: boolean, multiple: boolean, order?: number | null, typeProperty?: { __typename?: 'SchemaFieldAsset', assetDefaultValue?: any | null } | { __typename?: 'SchemaFieldBool', defaultValue?: any | null } | { __typename?: 'SchemaFieldCheckbox', defaultValue?: any | null } | { __typename?: 'SchemaFieldDate', defaultValue?: any | null } | { __typename?: 'SchemaFieldGroup', groupId: string } | { __typename?: 'SchemaFieldInteger', min?: number | null, max?: number | null, integerDefaultValue?: any | null } | { __typename?: 'SchemaFieldMarkdown', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldReference', modelId: string, schema: { __typename?: 'Schema', id: string, titleFieldId?: string | null }, correspondingField?: { __typename?: 'SchemaField', id: string, type: SchemaFieldType, title: string, key: string, description?: string | null, required: boolean, unique: boolean, multiple: boolean, order?: number | null } | null } | { __typename?: 'SchemaFieldRichText' } | { __typename?: 'SchemaFieldSelect', values: Array, selectDefaultValue?: any | null } | { __typename?: 'SchemaFieldTag', selectDefaultValue?: any | null, tags: Array<{ __typename?: 'SchemaFieldTagValue', id: string, name: string, color: SchemaFieldTagColor }> } | { __typename?: 'SchemaFieldText', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldTextArea', defaultValue?: any | null, maxLength?: number | null } | { __typename?: 'SchemaFieldURL', defaultValue?: any | null } | null }> } } | null> } }; export type GetModelQueryVariables = Exact<{ id: Scalars['ID']; @@ -5044,6 +5044,10 @@ export const GetModelsDocument = gql` } ... on SchemaFieldReference { modelId + schema { + id + titleFieldId + } correspondingField { id type diff --git a/web/src/gql/graphql.schema.json b/web/src/gql/graphql.schema.json index adf5e87e84..2f345ff374 100644 --- a/web/src/gql/graphql.schema.json +++ b/web/src/gql/graphql.schema.json @@ -1852,9 +1852,13 @@ "name": "description", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, @@ -1876,9 +1880,13 @@ "name": "key", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, @@ -1888,9 +1896,13 @@ "name": "required", "description": null, "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, @@ -1900,9 +1912,13 @@ "name": "title", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, @@ -12054,31 +12070,39 @@ "deprecationReason": null }, { - "name": "correspondingSchema", + "name": "modelId", "description": null, "args": [], "type": { - "kind": "OBJECT", - "name": "Schema", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "correspondingSchemaId", + "name": "schema", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Schema", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "modelId", + "name": "schemaId", "description": null, "args": [], "type": { @@ -12118,19 +12142,23 @@ "deprecationReason": null }, { - "name": "correspondingSchemaId", + "name": "modelId", "description": null, "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "modelId", + "name": "schemaId", "description": null, "type": { "kind": "NON_NULL", diff --git a/web/src/gql/queries/model.ts b/web/src/gql/queries/model.ts index 8e86281610..cab29f8e27 100644 --- a/web/src/gql/queries/model.ts +++ b/web/src/gql/queries/model.ts @@ -145,6 +145,10 @@ export const GET_MODELS = gql` } ... on SchemaFieldReference { modelId + schema { + id + titleFieldId + } correspondingField { id type