mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 12:52:16 +00:00
feat(relational): org metadata (#10761)
This pull request introduces a new feature that allows adding, updating, and querying metadata for organizations. The changes are primarily in the backend and include new database tables, repositories, and domain logic to support organization metadata. ## Changes * New `org_metadata` table: A new table `zitadel.org_metadata` is introduced to store metadata for organizations. It includes columns for `instance_id`, `org_id`, `key`, and `value`. The `value` is stored as a `BYTEA` type to allow for flexible data storage. * New `OrganizationMetadataRepository`: A new repository `OrganizationMetadataRepository` is created to handle all database operations for organization metadata. It provides methods to `Get`, `List`, `Set`, and `Remove` metadata. * New `org_metadata_relational_projection`: A new projection `org_metadata_relational_projection` is added to update the `zitadel.org_metadata` table based on events. It handles `MetadataSet`, `MetadataRemoved`, and `MetadataRemovedAll` events. * Updated `OrganizationRepository`: The `OrganizationRepository` is updated to support loading organization metadata. A new method `LoadMetadata` is added to enable joining the `org_metadata` table when querying for organizations. * Updated Organization domain: The Organization domain model is updated to include a new field `Metadata` of type `[]*OrganizationMetadata`. ## Additional Info * Extensible Design: The new metadata feature is designed to be extensible, allowing for future enhancements such as indexing on specific JSON fields within the `value` column. * closes https://github.com/zitadel/zitadel/issues/10206 * closes https://github.com/zitadel/zitadel/issues/10214
This commit is contained in:
@@ -21,6 +21,11 @@ func wrapError(err error) error {
|
||||
return database.NewMultipleRowsFoundError(err)
|
||||
}
|
||||
|
||||
var pgxErr *pgconn.PgError
|
||||
if errors.As(err, &pgxErr) {
|
||||
return wrapPgError(pgxErr)
|
||||
}
|
||||
|
||||
// scany only exports its errors as strings
|
||||
if strings.HasPrefix(err.Error(), "scany: expected 1 row, got: ") {
|
||||
return database.NewMultipleRowsFoundError(err)
|
||||
@@ -29,23 +34,24 @@ func wrapError(err error) error {
|
||||
return database.NewScanError(err)
|
||||
}
|
||||
|
||||
var pgxErr *pgconn.PgError
|
||||
if !errors.As(err, &pgxErr) {
|
||||
return database.NewUnknownError(err)
|
||||
}
|
||||
switch pgxErr.Code {
|
||||
// 23514: check_violation - A value violates a CHECK constraint.
|
||||
case "23514":
|
||||
return database.NewCheckError(pgxErr.TableName, pgxErr.ConstraintName, pgxErr)
|
||||
// 23505: unique_violation - A value violates a UNIQUE constraint.
|
||||
case "23505":
|
||||
return database.NewUniqueError(pgxErr.TableName, pgxErr.ConstraintName, pgxErr)
|
||||
// 23503: foreign_key_violation - A value violates a foreign key constraint.
|
||||
case "23503":
|
||||
return database.NewForeignKeyError(pgxErr.TableName, pgxErr.ConstraintName, pgxErr)
|
||||
// 23502: not_null_violation - A value violates a NOT NULL constraint.
|
||||
case "23502":
|
||||
return database.NewNotNullError(pgxErr.TableName, pgxErr.ConstraintName, pgxErr)
|
||||
}
|
||||
return database.NewUnknownError(err)
|
||||
}
|
||||
|
||||
func wrapPgError(err *pgconn.PgError) error {
|
||||
switch err.Code {
|
||||
// 23514: check_violation - A value violates a CHECK constraint.
|
||||
case "23514":
|
||||
return database.NewCheckError(err.TableName, err.ConstraintName, err)
|
||||
// 23505: unique_violation - A value violates a UNIQUE constraint.
|
||||
case "23505":
|
||||
return database.NewUniqueError(err.TableName, err.ConstraintName, err)
|
||||
// 23503: foreign_key_violation - A value violates a foreign key constraint.
|
||||
case "23503":
|
||||
return database.NewForeignKeyError(err.TableName, err.ConstraintName, err)
|
||||
// 23502: not_null_violation - A value violates a NOT NULL constraint.
|
||||
case "23502":
|
||||
return database.NewNotNullError(err.TableName, err.ConstraintName, err)
|
||||
default:
|
||||
return database.NewUnknownError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 007_org_metadata_table/up.sql
|
||||
up007OrgMetadataTable string
|
||||
//go:embed 007_org_metadata_table/down.sql
|
||||
down007OrgMetadataTable string
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerSQLMigration(7, up007OrgMetadataTable, down007OrgMetadataTable)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS zitadel.organization_metadata;
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE zitadel.organization_metadata (
|
||||
instance_id TEXT NOT NULL
|
||||
, organization_id TEXT NOT NULL
|
||||
, key TEXT NOT NULL CHECK (key <> '')
|
||||
, value BYTEA NOT NULL
|
||||
|
||||
, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
|
||||
, PRIMARY KEY (instance_id, organization_id, key)
|
||||
|
||||
, CONSTRAINT fk_organization_metadata_org FOREIGN KEY (instance_id, organization_id) REFERENCES zitadel.organizations (instance_id, id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_organization_metadata_key ON zitadel.organization_metadata (key);
|
||||
CREATE INDEX idx_organization_metadata_value ON zitadel.organization_metadata (sha256(value));
|
||||
|
||||
-- TODO(adlerhurst): these indexes can currently not be used by Postgres, because of the type conversion
|
||||
-- the value can be a json but doesn't have to be.
|
||||
-- CREATE INDEX idx_organization_metadata_value_number ON zitadel.organization_metadata ((value::NUMERIC)) WHERE jsonb_typeof(value) = 'number';
|
||||
-- CREATE INDEX idx_organization_metadata_value_string ON zitadel.organization_metadata ((value#>>'{}')) WHERE jsonb_typeof(value) = 'string';
|
||||
-- CREATE INDEX idx_organization_metadata_value_boolean ON zitadel.organization_metadata ((value::BOOLEAN)) WHERE jsonb_typeof(value) = 'boolean';
|
||||
|
||||
CREATE TRIGGER trg_set_updated_at_organization_metadata
|
||||
BEFORE INSERT OR UPDATE ON zitadel.organization_metadata
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.updated_at IS NULL)
|
||||
EXECUTE FUNCTION zitadel.set_updated_at();
|
||||
Reference in New Issue
Block a user