diff --git a/pkg/coredata/framework.go b/pkg/coredata/framework.go index 8ae7bef1c..13fd2c123 100644 --- a/pkg/coredata/framework.go +++ b/pkg/coredata/framework.go @@ -16,6 +16,7 @@ package coredata import ( "context" + "errors" "fmt" "maps" "time" @@ -23,6 +24,7 @@ import ( "github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/page" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" "go.gearno.de/kit/pg" ) @@ -38,8 +40,17 @@ type ( } Frameworks []*Framework + + ErrFrameworkReferenceIDAlreadyExists struct { + ReferenceID string + OrganizationID gid.GID + } ) +func (e ErrFrameworkReferenceIDAlreadyExists) Error() string { + return fmt.Sprintf("framework with reference ID %q already exists for organization %s", e.ReferenceID, e.OrganizationID) +} + func (f *Framework) CursorKey(orderBy FrameworkOrderField) page.CursorKey { switch orderBy { case FrameworkOrderFieldCreatedAt: @@ -175,7 +186,22 @@ VALUES ( "updated_at": f.UpdatedAt, } _, err := conn.Exec(ctx, q, args) - return err + + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgErr.Code == "23505" && pgErr.ConstraintName == "frameworks_org_ref_unique" { + return &ErrFrameworkReferenceIDAlreadyExists{ + ReferenceID: f.ReferenceID, + OrganizationID: f.OrganizationID, + } + } + } + + return err + } + + return nil } func (f Framework) Delete( diff --git a/pkg/coredata/migrations/20250331T150600Z.sql b/pkg/coredata/migrations/20250331T150600Z.sql new file mode 100644 index 000000000..792c59805 --- /dev/null +++ b/pkg/coredata/migrations/20250331T150600Z.sql @@ -0,0 +1,9 @@ +ALTER TABLE frameworks + ALTER COLUMN reference_id SET NOT NULL, + DROP CONSTRAINT IF EXISTS frameworks_reference_id_unique, + ADD CONSTRAINT frameworks_org_ref_unique UNIQUE (organization_id, reference_id); + +ALTER TABLE controls + ALTER COLUMN reference_id DROP DEFAULT, + DROP CONSTRAINT IF EXISTS controls_reference_id_unique, + ADD CONSTRAINT controls_framework_ref_unique UNIQUE (framework_id, reference_id); \ No newline at end of file