feat(rt): project repository (#10789)

# Which Problems Are Solved

Add projects to the relational tables

# How the Problems Are Solved

- Define table migrations
- Define and implement Project and Project Role repositories.
- Provide projection handlers to populate the relational tables.

# Additional Changes

- Statement Builder now has a constructor which allows setting of a base
query with arguments.
- Certain operations, like Get, Update and Delete require the Primary
Key to be set as conditions. However, this requires knowledge of the
implementation and table definition. This PR proposes an additional
condition for repositories: `PrimaryKeyCondition`. This gives clarity on
the required IDs for these operations.
- Added couple of helpers to the repository package, to allow more DRY
code.
- getOne / getMany: generic functions for query execution and scanning.
- checkRestrictingColumns, checkPkCondition: simplify condition
checking, instead of using ladders of conditionals.
- Added a couple of helpers to the repository test package:
  - Transaction, savepoint and rollback helpers.
- Create instance and organization helpers for objects that depend on
them (like projects).

# Additional Context

- after https://github.com/zitadel/zitadel/pull/10809
- closes #10765
This commit is contained in:
Tim Möhlmann
2025-10-01 09:47:04 +00:00
committed by GitHub
parent 28db24fa67
commit a45908b364
21 changed files with 2512 additions and 12 deletions
@@ -0,0 +1,16 @@
package migration
import (
_ "embed"
)
var (
//go:embed 006_projects_table/up.sql
up006ProjectsTable string
//go:embed 006_projects_table/down.sql
down006ProjectsTable string
)
func init() {
registerSQLMigration(6, up006ProjectsTable, down006ProjectsTable)
}
@@ -0,0 +1,5 @@
DROP TRIGGER trigger_set_updated_at ON zitadel.project_roles;
DROP TRIGGER trigger_set_updated_at ON zitadel.projects;
DROP TABLE zitadel.project_roles;
DROP TABLE zitadel.projects;
DROP TYPE zitadel.project_state;
@@ -0,0 +1,61 @@
CREATE TYPE zitadel.project_state AS ENUM (
'active',
'inactive'
);
CREATE TABLE zitadel.projects(
instance_id TEXT NOT NULL
, organization_id TEXT NOT NULL
, id TEXT NOT NULL CHECK (id <> '')
, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
, name TEXT NOT NULL CHECK (name <> '')
, state zitadel.project_state NOT NULL
-- API: project_role_assertion
, should_assert_role BOOLEAN NOT NULL DEFAULT FALSE
-- API: authorization_required
, is_authorization_required BOOLEAN NOT NULL DEFAULT FALSE
-- API: project_access_required
, is_project_access_required BOOLEAN NOT NULL DEFAULT FALSE
--API: private_labeling_setting
, used_labeling_setting_owner SMALLINT
, PRIMARY KEY (instance_id, id)
, UNIQUE (instance_id, organization_id, id)
, FOREIGN KEY (instance_id, organization_id) REFERENCES zitadel.organizations(instance_id, id) ON DELETE CASCADE
);
CREATE TABLE zitadel.project_roles(
instance_id TEXT NOT NULL
, organization_id TEXT NOT NULL
, project_id TEXT NOT NULL
, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
-- API: role_key
, key TEXT NOT NULL CHECK (key <> '')
-- API: display_name
, display_name TEXT NOT NULL CHECK (display_name <> '')
-- API: group
-- group is a reserved keyword in PostgreSQL
, role_group TEXT
, PRIMARY KEY (instance_id, project_id, key)
, UNIQUE (instance_id, organization_id, project_id, key)
, FOREIGN KEY (instance_id, organization_id, project_id) REFERENCES zitadel.projects(instance_id, organization_id, id) ON DELETE CASCADE
);
CREATE TRIGGER trigger_set_updated_at
BEFORE UPDATE ON zitadel.projects
FOR EACH ROW
WHEN (NEW.updated_at IS NULL)
EXECUTE FUNCTION zitadel.set_updated_at();
CREATE TRIGGER trigger_set_updated_at
BEFORE UPDATE ON zitadel.project_roles
FOR EACH ROW
WHEN (NEW.updated_at IS NULL)
EXECUTE FUNCTION zitadel.set_updated_at();