diff --git a/app/controlplane/internal/biz/workflow_integration_test.go b/app/controlplane/internal/biz/workflow_integration_test.go index 117050f08..7952a2a83 100644 --- a/app/controlplane/internal/biz/workflow_integration_test.go +++ b/app/controlplane/internal/biz/workflow_integration_test.go @@ -56,6 +56,27 @@ func (s *workflowIntegrationTestSuite) TestContractLatestAvailable() { }) } +func (s *workflowIntegrationTestSuite) TestCreateDuplicatedName() { + ctx := context.Background() + + const workflowName = "name" + existingWorkflow, err := s.Workflow.Create(ctx, &biz.WorkflowCreateOpts{OrgID: s.org.ID, Name: workflowName}) + require.NoError(s.T(), err) + + s.Run("can't create a workflow with the same name", func() { + _, err = s.Workflow.Create(ctx, &biz.WorkflowCreateOpts{OrgID: s.org.ID, Name: workflowName}) + s.ErrorContains(err, "name already taken") + }) + + s.Run("but if we delete it we can", func() { + err = s.Workflow.Delete(ctx, s.org.ID, existingWorkflow.ID.String()) + require.NoError(s.T(), err) + + _, err = s.Workflow.Create(ctx, &biz.WorkflowCreateOpts{OrgID: s.org.ID, Name: workflowName}) + require.NoError(s.T(), err) + }) +} + func (s *workflowIntegrationTestSuite) TestCreate() { ctx := context.Background() testCases := []struct { diff --git a/app/controlplane/internal/biz/workflowcontract_integration_test.go b/app/controlplane/internal/biz/workflowcontract_integration_test.go index 930113faa..c01d7e283 100644 --- a/app/controlplane/internal/biz/workflowcontract_integration_test.go +++ b/app/controlplane/internal/biz/workflowcontract_integration_test.go @@ -116,6 +116,27 @@ func (s *workflowContractIntegrationTestSuite) TestUpdate() { } } +func (s *workflowContractIntegrationTestSuite) TestCreateDuplicatedName() { + ctx := context.Background() + + const contractName = "name" + contract, err := s.WorkflowContract.Create(ctx, &biz.WorkflowContractCreateOpts{OrgID: s.org.ID, Name: contractName}) + require.NoError(s.T(), err) + + s.Run("can't create a contract with the same name", func() { + _, err := s.WorkflowContract.Create(ctx, &biz.WorkflowContractCreateOpts{OrgID: s.org.ID, Name: contractName}) + s.ErrorContains(err, "name already taken") + }) + + s.Run("but if we delete it we can", func() { + err = s.WorkflowContract.Delete(ctx, s.org.ID, contract.ID.String()) + require.NoError(s.T(), err) + + _, err := s.WorkflowContract.Create(ctx, &biz.WorkflowContractCreateOpts{OrgID: s.org.ID, Name: contractName}) + require.NoError(s.T(), err) + }) +} + func (s *workflowContractIntegrationTestSuite) TestCreate() { ctx := context.Background() diff --git a/app/controlplane/internal/data/ent/migrate/migrations/20240312141340.sql b/app/controlplane/internal/data/ent/migrate/migrations/20240312141340.sql index 1bfa458e7..8b4be386f 100644 --- a/app/controlplane/internal/data/ent/migrate/migrations/20240312141340.sql +++ b/app/controlplane/internal/data/ent/migrate/migrations/20240312141340.sql @@ -11,7 +11,7 @@ SET name = regexp_replace( -- and project UPDATE workflows SET project = regexp_replace( - lower(name), + lower(project), '[^a-z0-9-]', '-', 'g' diff --git a/app/controlplane/internal/data/ent/migrate/migrations/20240313115221.sql b/app/controlplane/internal/data/ent/migrate/migrations/20240313115221.sql new file mode 100644 index 000000000..e989f1d92 --- /dev/null +++ b/app/controlplane/internal/data/ent/migrate/migrations/20240313115221.sql @@ -0,0 +1,8 @@ +-- Drop index "workflowcontract_name_organization_workflow_contracts" from table: "workflow_contracts" +DROP INDEX "workflowcontract_name_organization_workflow_contracts"; +-- Create index "workflowcontract_name_organization_workflow_contracts" to table: "workflow_contracts" +CREATE UNIQUE INDEX "workflowcontract_name_organization_workflow_contracts" ON "workflow_contracts" ("name", "organization_workflow_contracts") WHERE (deleted_at IS NULL); +-- Drop index "workflow_name_organization_id" from table: "workflows" +DROP INDEX "workflow_name_organization_id"; +-- Create index "workflow_name_organization_id" to table: "workflows" +CREATE UNIQUE INDEX "workflow_name_organization_id" ON "workflows" ("name", "organization_id") WHERE (deleted_at IS NULL); diff --git a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum index e0f9b3166..d671b550b 100644 --- a/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum +++ b/app/controlplane/internal/data/ent/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:jlgZQ/R2/gTBkWCCkoXvtWydFDgCgQz2vjAVrYvW/pg= +h1:9ZaVZApcnCY4PP6Ahf4zW9sH3MKuvi4eSJkRBlog1X0= 20230706165452_init-schema.sql h1:VvqbNFEQnCvUVyj2iDYVQQxDM0+sSXqocpt/5H64k8M= 20230710111950-cas-backend.sql h1:A8iBuSzZIEbdsv9ipBtscZQuaBp3V5/VMw7eZH6GX+g= 20230712094107-cas-backends-workflow-runs.sql h1:a5rzxpVGyd56nLRSsKrmCFc9sebg65RWzLghKHh5xvI= @@ -26,5 +26,6 @@ h1:jlgZQ/R2/gTBkWCCkoXvtWydFDgCgQz2vjAVrYvW/pg= 20240303073902.sql h1:vTC9GmUjsyYzOmsEkLcVLvu+h5iJAohQnGFl+OoQLHQ= 20240303145130.sql h1:IY21A96Cq7wrNXkLWqhwNGXWcJVxUG7v6PorhOxMvao= 20240312102059.sql h1:nHApYqyVHcdHHivQFDw+AzPTVa4Gn3NHqmZnxVeZsoA= -20240312141340.sql h1:QDVfhXAD0//TikLmOWM6hdXiEGeahrjm1xeyHlYu9f8= -20240312211838.sql h1:l1anepk4TgpuRYGdcysltfM9pMVzqe/x6/QuSkqGV6w= +20240312141340.sql h1:bzZd5uvibbLZcpjam/wRwr1aF+zKMF2O+wELBAraDrU= +20240312211838.sql h1:cCiivFnB5mMySOzOgD72ttWGqp3a4IVWwvg+k/TNHYo= +20240313115221.sql h1:mhlM4LpGHmbicQ7m/13Iw7c6pZRBzvd14ojrUTe/knM= diff --git a/app/controlplane/internal/data/ent/migrate/schema.go b/app/controlplane/internal/data/ent/migrate/schema.go index 6cdac3bf4..5de8c5325 100644 --- a/app/controlplane/internal/data/ent/migrate/schema.go +++ b/app/controlplane/internal/data/ent/migrate/schema.go @@ -3,6 +3,7 @@ package migrate import ( + "entgo.io/ent/dialect/entsql" "entgo.io/ent/dialect/sql/schema" "entgo.io/ent/schema/field" ) @@ -331,6 +332,9 @@ var ( Name: "workflow_name_organization_id", Unique: true, Columns: []*schema.Column{WorkflowsColumns[1], WorkflowsColumns[9]}, + Annotation: &entsql.IndexAnnotation{ + Where: "deleted_at IS NULL", + }, }, }, } @@ -361,6 +365,9 @@ var ( Name: "workflowcontract_name_organization_workflow_contracts", Unique: true, Columns: []*schema.Column{WorkflowContractsColumns[1], WorkflowContractsColumns[5]}, + Annotation: &entsql.IndexAnnotation{ + Where: "deleted_at IS NULL", + }, }, }, } diff --git a/app/controlplane/internal/data/ent/schema/workflow.go b/app/controlplane/internal/data/ent/schema/workflow.go index 5a7ef6500..8467e13dd 100644 --- a/app/controlplane/internal/data/ent/schema/workflow.go +++ b/app/controlplane/internal/data/ent/schema/workflow.go @@ -70,7 +70,9 @@ func (Workflow) Edges() []ent.Edge { func (Workflow) Indexes() []ent.Index { return []ent.Index{ - // names are unique within an organization - index.Fields("name").Edges("organization").Unique(), + // names are unique within a organization and affects only to non-deleted items + index.Fields("name").Edges("organization").Unique().Annotations( + entsql.IndexWhere("deleted_at IS NULL"), + ), } } diff --git a/app/controlplane/internal/data/ent/schema/workflowcontract.go b/app/controlplane/internal/data/ent/schema/workflowcontract.go index c5a2c0800..2bcbd4943 100644 --- a/app/controlplane/internal/data/ent/schema/workflowcontract.go +++ b/app/controlplane/internal/data/ent/schema/workflowcontract.go @@ -61,7 +61,9 @@ func (WorkflowContract) Edges() []ent.Edge { func (WorkflowContract) Indexes() []ent.Index { return []ent.Index{ - // names are unique within an organization - index.Fields("name").Edges("organization").Unique(), + // names are unique within a organization and affects only to non-deleted items + index.Fields("name").Edges("organization").Unique().Annotations( + entsql.IndexWhere("deleted_at IS NULL"), + ), } }