From ff8d245d31fbfa4d5d746aa9ec8be96991230f62 Mon Sep 17 00:00:00 2001 From: James Kwon <96548424+hongil0316@users.noreply.github.com> Date: Tue, 28 May 2024 16:02:05 -0400 Subject: [PATCH] Test adding a test_field for versioned migration changes --- .github/workflows/migration-ci.yaml | 37 ++++++++++++--- README.md | 11 +++++ ent/migrate/schema.go | 3 +- ent/mutation.go | 72 ++++++++++++++++++++++++++++- ent/node.go | 15 +++++- ent/node/node.go | 5 ++ ent/node_create.go | 57 +++++++++++++++++++++++ ent/node_update.go | 40 ++++++++++++++++ ent/runtime.go | 4 ++ ent/schema/node.go | 3 ++ main.go | 16 ++++--- 11 files changed, 246 insertions(+), 17 deletions(-) diff --git a/.github/workflows/migration-ci.yaml b/.github/workflows/migration-ci.yaml index e3e44b1..a0ef23d 100644 --- a/.github/workflows/migration-ci.yaml +++ b/.github/workflows/migration-ci.yaml @@ -1,14 +1,13 @@ name: Atlas CI on: # Run whenever code is changed in the main branch, - # change this to your root branch. - # push: - # branches: - # - main + push: + branches: + - main # Run on PRs where something changed under the `ent/migrate/migrations/` directory. pull_request: paths: - - 'ent/migrate/migrations/*' + - 'ent/**' jobs: lint: services: @@ -30,8 +29,32 @@ jobs: - uses: actions/checkout@v3.0.1 with: fetch-depth: 0 # Mandatory unless "latest" is set below. - - uses: ariga/atlas-action@v0 + - uses: ariga/atlas-action@v1.0.11 with: dir: ent/migrate/migrations dir-format: golang-migrate # Or: atlas, goose, dbmate - dev-url: postgres://postgres:pass@localhost:5432/test?sslmode=disable \ No newline at end of file + dev-url: postgres://postgres:pass@localhost:5432/test?sslmode=disable + + - name: Check for migration changes + id: check_migrations + run: | + # List files changed in the PR + echo "Checking for changes between ${{ github.event.pull_request.base.sha }} and ${{ github.sha }}" + changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) + echo "Changed files: $changed_files" + + # Check for changes in 'ent/schema' + schema_changes=$(echo "$changed_files" | grep '^ent/schema' || true) + echo "Schema changes: $schema_changes" + + # Check for changes in 'ent/migrate/migrations' + migration_changes=$(echo "$changed_files" | grep '^ent/migrate/migrations' || true) + echo "Migration changes: $migration_changes" + + # If there are schema changes but no migration changes, fail the check + if [ -n "$schema_changes" ] && [ -z "$migration_changes" ]; then + echo "::error::Changes in 'ent/schema' require corresponding changes in 'ent/migrate/migrations'" + exit 1 + else + echo "Check passed: Schema changes are accompanied by migration changes." + fi \ No newline at end of file diff --git a/README.md b/README.md index 75a7265..7ef61b2 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,17 @@ Or manually run: `go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/upsert --feature sql/lock ./ent/schema` +### Generate Migration Files + +Run this command to generate migration files needed for staging/prod database schema changes: + +```shell +atlas migrate diff migration \ + --dir "file://ent/migrate/migrations" \ + --to "ent://ent/schema" \ + --dev-url "docker://postgres/15/test?search_path=public" +``` + ## API Spec Change (openapi.yml) ### Regenerate code diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index 87c9449..bda4c18 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -82,6 +82,7 @@ var ( {Name: "repository_url", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}}, {Name: "icon_url", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, {Name: "tags", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "test_field", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "text"}}, {Name: "publisher_id", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}}, } // NodesTable holds the schema information for the "nodes" table. @@ -92,7 +93,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "nodes_publishers_nodes", - Columns: []*schema.Column{NodesColumns[10]}, + Columns: []*schema.Column{NodesColumns[11]}, RefColumns: []*schema.Column{PublishersColumns[0]}, OnDelete: schema.NoAction, }, diff --git a/ent/mutation.go b/ent/mutation.go index 8a60a91..d2f3cb6 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -2110,6 +2110,8 @@ type NodeMutation struct { icon_url *string tags *[]string appendtags []string + test_field *[]string + appendtest_field []string clearedFields map[string]struct{} publisher *string clearedpublisher bool @@ -2639,6 +2641,57 @@ func (m *NodeMutation) ResetTags() { m.appendtags = nil } +// SetTestField sets the "test_field" field. +func (m *NodeMutation) SetTestField(s []string) { + m.test_field = &s + m.appendtest_field = nil +} + +// TestField returns the value of the "test_field" field in the mutation. +func (m *NodeMutation) TestField() (r []string, exists bool) { + v := m.test_field + if v == nil { + return + } + return *v, true +} + +// OldTestField returns the old "test_field" field's value of the Node entity. +// If the Node object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *NodeMutation) OldTestField(ctx context.Context) (v []string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTestField is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTestField requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTestField: %w", err) + } + return oldValue.TestField, nil +} + +// AppendTestField adds s to the "test_field" field. +func (m *NodeMutation) AppendTestField(s []string) { + m.appendtest_field = append(m.appendtest_field, s...) +} + +// AppendedTestField returns the list of values that were appended to the "test_field" field in this mutation. +func (m *NodeMutation) AppendedTestField() ([]string, bool) { + if len(m.appendtest_field) == 0 { + return nil, false + } + return m.appendtest_field, true +} + +// ResetTestField resets all changes to the "test_field" field. +func (m *NodeMutation) ResetTestField() { + m.test_field = nil + m.appendtest_field = nil +} + // ClearPublisher clears the "publisher" edge to the Publisher entity. func (m *NodeMutation) ClearPublisher() { m.clearedpublisher = true @@ -2754,7 +2807,7 @@ func (m *NodeMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *NodeMutation) Fields() []string { - fields := make([]string, 0, 10) + fields := make([]string, 0, 11) if m.create_time != nil { fields = append(fields, node.FieldCreateTime) } @@ -2785,6 +2838,9 @@ func (m *NodeMutation) Fields() []string { if m.tags != nil { fields = append(fields, node.FieldTags) } + if m.test_field != nil { + fields = append(fields, node.FieldTestField) + } return fields } @@ -2813,6 +2869,8 @@ func (m *NodeMutation) Field(name string) (ent.Value, bool) { return m.IconURL() case node.FieldTags: return m.Tags() + case node.FieldTestField: + return m.TestField() } return nil, false } @@ -2842,6 +2900,8 @@ func (m *NodeMutation) OldField(ctx context.Context, name string) (ent.Value, er return m.OldIconURL(ctx) case node.FieldTags: return m.OldTags(ctx) + case node.FieldTestField: + return m.OldTestField(ctx) } return nil, fmt.Errorf("unknown Node field %s", name) } @@ -2921,6 +2981,13 @@ func (m *NodeMutation) SetField(name string, value ent.Value) error { } m.SetTags(v) return nil + case node.FieldTestField: + v, ok := value.([]string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTestField(v) + return nil } return fmt.Errorf("unknown Node field %s", name) } @@ -3021,6 +3088,9 @@ func (m *NodeMutation) ResetField(name string) error { case node.FieldTags: m.ResetTags() return nil + case node.FieldTestField: + m.ResetTestField() + return nil } return fmt.Errorf("unknown Node field %s", name) } diff --git a/ent/node.go b/ent/node.go index 00f0a94..3d49bca 100644 --- a/ent/node.go +++ b/ent/node.go @@ -39,6 +39,8 @@ type Node struct { IconURL string `json:"icon_url,omitempty"` // Tags holds the value of the "tags" field. Tags []string `json:"tags,omitempty"` + // TestField holds the value of the "test_field" field. + TestField []string `json:"test_field,omitempty"` // Edges holds the relations/edges for other nodes in the graph. // The values are being populated by the NodeQuery when eager-loading is set. Edges NodeEdges `json:"edges"` @@ -81,7 +83,7 @@ func (*Node) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case node.FieldTags: + case node.FieldTags, node.FieldTestField: values[i] = new([]byte) case node.FieldID, node.FieldPublisherID, node.FieldName, node.FieldDescription, node.FieldAuthor, node.FieldLicense, node.FieldRepositoryURL, node.FieldIconURL: values[i] = new(sql.NullString) @@ -170,6 +172,14 @@ func (n *Node) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field tags: %w", err) } } + case node.FieldTestField: + if value, ok := values[i].(*[]byte); !ok { + return fmt.Errorf("unexpected type %T for field test_field", values[i]) + } else if value != nil && len(*value) > 0 { + if err := json.Unmarshal(*value, &n.TestField); err != nil { + return fmt.Errorf("unmarshal field test_field: %w", err) + } + } default: n.selectValues.Set(columns[i], values[i]) } @@ -245,6 +255,9 @@ func (n *Node) String() string { builder.WriteString(", ") builder.WriteString("tags=") builder.WriteString(fmt.Sprintf("%v", n.Tags)) + builder.WriteString(", ") + builder.WriteString("test_field=") + builder.WriteString(fmt.Sprintf("%v", n.TestField)) builder.WriteByte(')') return builder.String() } diff --git a/ent/node/node.go b/ent/node/node.go index 2fe6325..c7dde21 100644 --- a/ent/node/node.go +++ b/ent/node/node.go @@ -34,6 +34,8 @@ const ( FieldIconURL = "icon_url" // FieldTags holds the string denoting the tags field in the database. FieldTags = "tags" + // FieldTestField holds the string denoting the test_field field in the database. + FieldTestField = "test_field" // EdgePublisher holds the string denoting the publisher edge name in mutations. EdgePublisher = "publisher" // EdgeVersions holds the string denoting the versions edge name in mutations. @@ -69,6 +71,7 @@ var Columns = []string{ FieldRepositoryURL, FieldIconURL, FieldTags, + FieldTestField, } // ValidColumn reports if the column name is valid (part of the table columns). @@ -90,6 +93,8 @@ var ( UpdateDefaultUpdateTime func() time.Time // DefaultTags holds the default value on creation for the "tags" field. DefaultTags []string + // DefaultTestField holds the default value on creation for the "test_field" field. + DefaultTestField []string ) // OrderOption defines the ordering options for the Node queries. diff --git a/ent/node_create.go b/ent/node_create.go index e413ea8..b3e271c 100644 --- a/ent/node_create.go +++ b/ent/node_create.go @@ -126,6 +126,12 @@ func (nc *NodeCreate) SetTags(s []string) *NodeCreate { return nc } +// SetTestField sets the "test_field" field. +func (nc *NodeCreate) SetTestField(s []string) *NodeCreate { + nc.mutation.SetTestField(s) + return nc +} + // SetID sets the "id" field. func (nc *NodeCreate) SetID(s string) *NodeCreate { nc.mutation.SetID(s) @@ -199,6 +205,10 @@ func (nc *NodeCreate) defaults() { v := node.DefaultTags nc.mutation.SetTags(v) } + if _, ok := nc.mutation.TestField(); !ok { + v := node.DefaultTestField + nc.mutation.SetTestField(v) + } } // check runs all checks and user-defined validators on the builder. @@ -224,6 +234,9 @@ func (nc *NodeCreate) check() error { if _, ok := nc.mutation.Tags(); !ok { return &ValidationError{Name: "tags", err: errors.New(`ent: missing required field "Node.tags"`)} } + if _, ok := nc.mutation.TestField(); !ok { + return &ValidationError{Name: "test_field", err: errors.New(`ent: missing required field "Node.test_field"`)} + } if _, ok := nc.mutation.PublisherID(); !ok { return &ValidationError{Name: "publisher", err: errors.New(`ent: missing required edge "Node.publisher"`)} } @@ -299,6 +312,10 @@ func (nc *NodeCreate) createSpec() (*Node, *sqlgraph.CreateSpec) { _spec.SetField(node.FieldTags, field.TypeJSON, value) _node.Tags = value } + if value, ok := nc.mutation.TestField(); ok { + _spec.SetField(node.FieldTestField, field.TypeJSON, value) + _node.TestField = value + } if nodes := nc.mutation.PublisherIDs(); len(nodes) > 0 { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -510,6 +527,18 @@ func (u *NodeUpsert) UpdateTags() *NodeUpsert { return u } +// SetTestField sets the "test_field" field. +func (u *NodeUpsert) SetTestField(v []string) *NodeUpsert { + u.Set(node.FieldTestField, v) + return u +} + +// UpdateTestField sets the "test_field" field to the value that was provided on create. +func (u *NodeUpsert) UpdateTestField() *NodeUpsert { + u.SetExcluded(node.FieldTestField) + return u +} + // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. // Using this option is equivalent to using: // @@ -708,6 +737,20 @@ func (u *NodeUpsertOne) UpdateTags() *NodeUpsertOne { }) } +// SetTestField sets the "test_field" field. +func (u *NodeUpsertOne) SetTestField(v []string) *NodeUpsertOne { + return u.Update(func(s *NodeUpsert) { + s.SetTestField(v) + }) +} + +// UpdateTestField sets the "test_field" field to the value that was provided on create. +func (u *NodeUpsertOne) UpdateTestField() *NodeUpsertOne { + return u.Update(func(s *NodeUpsert) { + s.UpdateTestField() + }) +} + // Exec executes the query. func (u *NodeUpsertOne) Exec(ctx context.Context) error { if len(u.create.conflict) == 0 { @@ -1073,6 +1116,20 @@ func (u *NodeUpsertBulk) UpdateTags() *NodeUpsertBulk { }) } +// SetTestField sets the "test_field" field. +func (u *NodeUpsertBulk) SetTestField(v []string) *NodeUpsertBulk { + return u.Update(func(s *NodeUpsert) { + s.SetTestField(v) + }) +} + +// UpdateTestField sets the "test_field" field to the value that was provided on create. +func (u *NodeUpsertBulk) UpdateTestField() *NodeUpsertBulk { + return u.Update(func(s *NodeUpsert) { + s.UpdateTestField() + }) +} + // Exec executes the query. func (u *NodeUpsertBulk) Exec(ctx context.Context) error { if u.create.err != nil { diff --git a/ent/node_update.go b/ent/node_update.go index 9fb6016..8b4e169 100644 --- a/ent/node_update.go +++ b/ent/node_update.go @@ -166,6 +166,18 @@ func (nu *NodeUpdate) AppendTags(s []string) *NodeUpdate { return nu } +// SetTestField sets the "test_field" field. +func (nu *NodeUpdate) SetTestField(s []string) *NodeUpdate { + nu.mutation.SetTestField(s) + return nu +} + +// AppendTestField appends s to the "test_field" field. +func (nu *NodeUpdate) AppendTestField(s []string) *NodeUpdate { + nu.mutation.AppendTestField(s) + return nu +} + // SetPublisher sets the "publisher" edge to the Publisher entity. func (nu *NodeUpdate) SetPublisher(p *Publisher) *NodeUpdate { return nu.SetPublisherID(p.ID) @@ -312,6 +324,14 @@ func (nu *NodeUpdate) sqlSave(ctx context.Context) (n int, err error) { sqljson.Append(u, node.FieldTags, value) }) } + if value, ok := nu.mutation.TestField(); ok { + _spec.SetField(node.FieldTestField, field.TypeJSON, value) + } + if value, ok := nu.mutation.AppendedTestField(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, node.FieldTestField, value) + }) + } if nu.mutation.PublisherCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, @@ -540,6 +560,18 @@ func (nuo *NodeUpdateOne) AppendTags(s []string) *NodeUpdateOne { return nuo } +// SetTestField sets the "test_field" field. +func (nuo *NodeUpdateOne) SetTestField(s []string) *NodeUpdateOne { + nuo.mutation.SetTestField(s) + return nuo +} + +// AppendTestField appends s to the "test_field" field. +func (nuo *NodeUpdateOne) AppendTestField(s []string) *NodeUpdateOne { + nuo.mutation.AppendTestField(s) + return nuo +} + // SetPublisher sets the "publisher" edge to the Publisher entity. func (nuo *NodeUpdateOne) SetPublisher(p *Publisher) *NodeUpdateOne { return nuo.SetPublisherID(p.ID) @@ -716,6 +748,14 @@ func (nuo *NodeUpdateOne) sqlSave(ctx context.Context) (_node *Node, err error) sqljson.Append(u, node.FieldTags, value) }) } + if value, ok := nuo.mutation.TestField(); ok { + _spec.SetField(node.FieldTestField, field.TypeJSON, value) + } + if value, ok := nuo.mutation.AppendedTestField(); ok { + _spec.AddModifier(func(u *sql.UpdateBuilder) { + sqljson.Append(u, node.FieldTestField, value) + }) + } if nuo.mutation.PublisherCleared() { edge := &sqlgraph.EdgeSpec{ Rel: sqlgraph.M2O, diff --git a/ent/runtime.go b/ent/runtime.go index 63e4103..b889465 100644 --- a/ent/runtime.go +++ b/ent/runtime.go @@ -78,6 +78,10 @@ func init() { nodeDescTags := nodeFields[8].Descriptor() // node.DefaultTags holds the default value on creation for the tags field. node.DefaultTags = nodeDescTags.Default.([]string) + // nodeDescTestField is the schema descriptor for test_field field. + nodeDescTestField := nodeFields[9].Descriptor() + // node.DefaultTestField holds the default value on creation for the test_field field. + node.DefaultTestField = nodeDescTestField.Default.([]string) nodeversionMixin := schema.NodeVersion{}.Mixin() nodeversionMixinFields0 := nodeversionMixin[0].Fields() _ = nodeversionMixinFields0 diff --git a/ent/schema/node.go b/ent/schema/node.go index 43efc2b..9a74694 100644 --- a/ent/schema/node.go +++ b/ent/schema/node.go @@ -43,6 +43,9 @@ func (Node) Fields() []ent.Field { field.Strings("tags").SchemaType(map[string]string{ dialect.Postgres: "text", }).Default([]string{}), + field.Strings("test_field").SchemaType(map[string]string{ + dialect.Postgres: "text", + }).Default([]string{}), } } diff --git a/main.go b/main.go index 8d04f47..95659bb 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,12 @@ package main import ( + "context" "fmt" "os" "registry-backend/config" "registry-backend/ent" + "registry-backend/ent/migrate" drip_logging "registry-backend/logging" "registry-backend/server" @@ -38,13 +40,13 @@ func main() { } defer client.Close() // Run the auto migration tool for localdev. - //if os.Getenv("DRIP_ENV") == "localdev" || os.Getenv("DRIP_ENV") == "staging" { - // log.Info().Msg("Running migrations") - // if err := client.Schema.Create(context.Background(), migrate.WithDropIndex(true), - // migrate.WithDropColumn(true)); err != nil { - // log.Fatal().Err(err).Msg("failed creating schema resources.") - // } - //} + if os.Getenv("DRIP_ENV") == "localdev" || os.Getenv("DRIP_ENV") == "staging" { + log.Info().Msg("Running migrations") + if err := client.Schema.Create(context.Background(), migrate.WithDropIndex(true), + migrate.WithDropColumn(true)); err != nil { + log.Fatal().Err(err).Msg("failed creating schema resources.") + } + } server := server.NewServer(client, &config) server.Start()