diff --git a/taco/internal/api/org_handler.go b/taco/internal/api/org_handler.go index 8552a792a..a561b7894 100644 --- a/taco/internal/api/org_handler.go +++ b/taco/internal/api/org_handler.go @@ -95,6 +95,7 @@ func (h *OrgHandler) CreateOrganization(c echo.Context) error { slog.Info("Creating organization", "name", req.Name, "displayName", req.DisplayName, + "externalOrgID", req.ExternalOrgID, "createdBy", userIDStr, ) diff --git a/taco/internal/query/types/models.go b/taco/internal/query/types/models.go index d3f1f5165..7ba221322 100644 --- a/taco/internal/query/types/models.go +++ b/taco/internal/query/types/models.go @@ -107,9 +107,9 @@ func (rut *RuleUnitTag) BeforeCreate(tx *gorm.DB) error { type Organization struct { ID string `gorm:"type:varchar(36);primaryKey"` - Name string `gorm:"type:varchar(255);not null;uniqueIndex"` // Unique identifier (e.g., "acme") - used in CLI and paths - DisplayName string `gorm:"type:varchar(255);not null"` // Friendly name (e.g., "Acme Corp") - shown in UI - ExternalOrgID *string `gorm:"type:varchar(500);uniqueIndex"` // External org identifier (optional, nullable) + Name string `gorm:"type:varchar(255);not null;index"` // Non-unique - multiple orgs can have same name (e.g., "Personal") + DisplayName string `gorm:"type:varchar(255);not null"` // Friendly name (e.g., "Acme Corp") - shown in UI + ExternalOrgID *string `gorm:"type:varchar(500);uniqueIndex"` // External org identifier (optional, nullable) - THIS is unique CreatedBy string `gorm:"type:varchar(255);not null"` CreatedAt time.Time UpdatedAt time.Time diff --git a/taco/internal/repositories/org_repository.go b/taco/internal/repositories/org_repository.go index 1417ae25c..fb9f069d4 100644 --- a/taco/internal/repositories/org_repository.go +++ b/taco/internal/repositories/org_repository.go @@ -57,16 +57,9 @@ func (r *orgRepository) Create(ctx context.Context, orgID, name, displayName, ex return nil, err } - // Check if org already exists by name (infrastructure logic) - var existing types.Organization - err := r.db.WithContext(ctx).Where(queryOrgByName, orgID).First(&existing).Error - if err == nil { - return nil, domain.ErrOrgExists - } - if !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, fmt.Errorf("failed to check existing org: %w", err) - } - + // Note: Name uniqueness is no longer enforced - multiple orgs can have the same name + // Only external_org_id must be unique (enforced by database constraint) + // Check if external org ID already exists (if provided) if externalOrgID != "" { var existingExternal types.Organization diff --git a/taco/migrations/mysql/20251030000000_remove_org_name_unique_constraint.sql b/taco/migrations/mysql/20251030000000_remove_org_name_unique_constraint.sql new file mode 100644 index 000000000..5db56b3e3 --- /dev/null +++ b/taco/migrations/mysql/20251030000000_remove_org_name_unique_constraint.sql @@ -0,0 +1,4 @@ +-- Modify "organizations" table +ALTER TABLE `organizations` DROP INDEX `idx_organizations_name`; +-- Modify "organizations" table +ALTER TABLE `organizations` ADD INDEX `idx_organizations_name` (`name`); diff --git a/taco/migrations/postgres/20251030000000_remove_org_name_unique_constraint.sql b/taco/migrations/postgres/20251030000000_remove_org_name_unique_constraint.sql new file mode 100644 index 000000000..44d0fc644 --- /dev/null +++ b/taco/migrations/postgres/20251030000000_remove_org_name_unique_constraint.sql @@ -0,0 +1,4 @@ +-- Drop index "idx_organizations_name" from table: "organizations" +DROP INDEX "public"."idx_organizations_name"; +-- Create index "idx_organizations_name" to table: "organizations" +CREATE INDEX "idx_organizations_name" ON "public"."organizations" ("name"); diff --git a/taco/migrations/sqlite/20251030000000_remove_org_name_unique_constraint.sql b/taco/migrations/sqlite/20251030000000_remove_org_name_unique_constraint.sql new file mode 100644 index 000000000..6d08b94f8 --- /dev/null +++ b/taco/migrations/sqlite/20251030000000_remove_org_name_unique_constraint.sql @@ -0,0 +1,25 @@ +-- Disable the enforcement of foreign-keys constraints +PRAGMA foreign_keys = off; +-- Create "new_organizations" table +CREATE TABLE `new_organizations` ( + `id` varchar NULL, + `name` varchar NOT NULL, + `display_name` varchar NOT NULL, + `external_org_id` varchar NULL, + `created_by` varchar NOT NULL, + `created_at` datetime NULL, + `updated_at` datetime NULL, + PRIMARY KEY (`id`) +); +-- Copy rows from old table "organizations" to new temporary table "new_organizations" +INSERT INTO `new_organizations` (`id`, `name`, `display_name`, `external_org_id`, `created_by`, `created_at`, `updated_at`) SELECT `id`, `name`, `display_name`, `external_org_id`, `created_by`, `created_at`, `updated_at` FROM `organizations`; +-- Drop "organizations" table after copying rows +DROP TABLE `organizations`; +-- Rename temporary table "new_organizations" to "organizations" +ALTER TABLE `new_organizations` RENAME TO `organizations`; +-- Create index "idx_organizations_external_org_id" to table: "organizations" +CREATE UNIQUE INDEX `idx_organizations_external_org_id` ON `organizations` (`external_org_id`); +-- Create index "idx_organizations_name" to table: "organizations" +CREATE INDEX `idx_organizations_name` ON `organizations` (`name`); +-- Enable back the enforcement of foreign-keys constraints +PRAGMA foreign_keys = on; diff --git a/ui/src/routes/api/auth/workos/webhooks.tsx b/ui/src/routes/api/auth/workos/webhooks.tsx index 028bce4e3..7dde9ed68 100644 --- a/ui/src/routes/api/auth/workos/webhooks.tsx +++ b/ui/src/routes/api/auth/workos/webhooks.tsx @@ -79,9 +79,9 @@ export const Route = createFileRoute('/api/auth/workos/webhooks')({ console.log(`Syncing organization ${orgName} to backend and statesman`); try { await syncOrgToBackend(invitation.organizationId!, orgName, null); - await syncOrgToStatesman(invitation.organizationId!, orgName, personalOrgDisplayName, invitation.userId!, invitation.userEmail!); + await syncOrgToStatesman(invitation.organizationId!, orgName, orgName, invitation.userId!, invitation.userEmail!); } catch (error) { - console.error(`Error syncing user to backend:`, error); + console.error(`Error syncing organization to backend:`, error); throw error; } }