Files
zitadel/backend/v3/storage/database/repository/project.go
T
SilvanandGitHub 07dbb48cc7 refactor: relational organization projection to use repository (#11621)
- Transitioned from using `handler.Statements` to a repository pattern
for managing organization data in migrations.
- Introduced a new method to set the updated timestamp for
organizations.
- Updated the projection logic to utilize the repository for creating,
updating, and deleting organization records.
2026-03-05 14:55:42 +01:00

311 lines
9.6 KiB
Go

package repository
import (
"context"
"time"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
type project struct{}
// ProjectRepository manages projects and project roles.
func ProjectRepository() domain.ProjectRepository {
return project{}
}
func (project) Role() domain.ProjectRoleRepository {
return projectRole{}
}
func (p project) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Project, error) {
builder, err := p.prepareGetQuery(opts)
if err != nil {
return nil, err
}
return getOne[domain.Project](ctx, client, builder)
}
func (p project) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Project, error) {
builder, err := p.prepareListQuery(opts)
if err != nil {
return nil, err
}
return getMany[domain.Project](ctx, client, builder)
}
func (p project) Create(ctx context.Context, client database.QueryExecutor, project *domain.Project) error {
var (
builder database.StatementBuilder
createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction
)
if !project.CreatedAt.IsZero() {
createdAt = project.CreatedAt
}
if !project.UpdatedAt.IsZero() {
updatedAt = project.UpdatedAt
}
builder.WriteString("INSERT INTO ")
builder.WriteString(p.qualifiedTableName())
builder.WriteString("(instance_id, organization_id, id, name, state, should_assert_role, is_authorization_required, is_project_access_required, used_labeling_setting_owner, created_at, updated_at) VALUES (")
builder.WriteArgs(
project.InstanceID,
project.OrganizationID,
project.ID,
project.Name,
project.State,
project.ShouldAssertRole,
project.IsAuthorizationRequired,
project.IsProjectAccessRequired,
project.UsedLabelingSettingOwner,
createdAt,
updatedAt,
)
builder.WriteString(`) RETURNING created_at, updated_at`)
return client.QueryRow(ctx, builder.String(), builder.Args()...).
Scan(&project.CreatedAt, &project.UpdatedAt)
}
func (p project) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
return updateOne(ctx, client, p, condition, changes...)
}
func (p project) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
return deleteOne(ctx, client, p, condition)
}
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
func (p project) SetUpdatedAt(updatedAt time.Time) database.Change {
return database.NewChange(p.UpdatedAtColumn(), updatedAt)
}
func (p project) SetName(name string) database.Change {
return database.NewChange(p.NameColumn(), name)
}
func (p project) SetState(state domain.ProjectState) database.Change {
return database.NewChange(p.StateColumn(), state)
}
func (p project) SetShouldAssertRole(shouldAssertRole bool) database.Change {
return database.NewChange(p.ShouldAssertRoleColumn(), shouldAssertRole)
}
func (p project) SetIsAuthorizationRequired(isAuthorizationRequired bool) database.Change {
return database.NewChange(p.IsAuthorizationRequiredColumn(), isAuthorizationRequired)
}
func (p project) SetIsProjectAccessRequired(isProjectAccessRequired bool) database.Change {
return database.NewChange(p.IsProjectAccessRequiredColumn(), isProjectAccessRequired)
}
func (p project) SetUsedLabelingSettingOwner(usedLabelingSettingOwner int16) database.Change {
return database.NewChange(p.UsedLabelingSettingOwnerColumn(), usedLabelingSettingOwner)
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
func (p project) PrimaryKeyCondition(instanceID, projectID string) database.Condition {
return database.And(
p.InstanceIDCondition(instanceID),
p.IDCondition(projectID),
)
}
func (p project) InstanceIDCondition(instanceID string) database.Condition {
return database.NewTextCondition(p.InstanceIDColumn(), database.TextOperationEqual, instanceID)
}
func (p project) OrganizationIDCondition(organizationID string) database.Condition {
return database.NewTextCondition(p.OrganizationIDColumn(), database.TextOperationEqual, organizationID)
}
func (p project) GrantedOrganizationIDCondition(organizationID string) database.Condition {
return database.NewTextCondition(p.GrantedOrganizationIDColumn(), database.TextOperationEqual, organizationID)
}
func (p project) IDCondition(projectID string) database.Condition {
return database.NewTextCondition(p.IDColumn(), database.TextOperationEqual, projectID)
}
func (p project) NameCondition(op database.TextOperation, name string) database.Condition {
return database.NewTextCondition(p.NameColumn(), op, name)
}
func (p project) StateCondition(state domain.ProjectState) database.Condition {
return database.NewTextCondition(p.StateColumn(), database.TextOperationEqual, state.String())
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
func (p project) qualifiedTableName() string {
return "zitadel.projects"
}
func (project) unqualifiedTableName() string {
return "projects"
}
// PrimaryKeyColumns implements the [pkRepository] interface
func (p project) PrimaryKeyColumns() []database.Column {
return []database.Column{
p.InstanceIDColumn(),
p.IDColumn(),
}
}
func (p project) InstanceIDColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "instance_id")
}
func (p project) OrganizationIDColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "organization_id")
}
func (p project) GrantedOrganizationIDColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "granted_organization_id")
}
func (p project) IDColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "id")
}
func (p project) CreatedAtColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "created_at")
}
func (p project) UpdatedAtColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "updated_at")
}
func (p project) NameColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "name")
}
func (p project) StateColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "state")
}
func (p project) ShouldAssertRoleColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "should_assert_role")
}
func (p project) IsAuthorizationRequiredColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "is_authorization_required")
}
func (p project) IsProjectAccessRequiredColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "is_project_access_required")
}
func (p project) UsedLabelingSettingOwnerColumn() database.Column {
return database.NewColumn(p.unqualifiedTableName(), "used_labeling_setting_owner")
}
// -------------------------------------------------------------
// helpers
// -------------------------------------------------------------
const queryProjectStmt = `SELECT
projects.instance_id,
projects.organization_id,
projects.id,
projects.created_at,
projects.updated_at,
projects.name,
projects.state,
projects.should_assert_role,
projects.is_authorization_required,
projects.is_project_access_required,
projects.used_labeling_setting_owner,
NULL::TEXT AS grant_id,
NULL::TEXT AS granted_organization_id,
NULL::zitadel.project_grant_state AS grant_state
FROM zitadel.projects`
func (p project) prepareGetQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
if err := checkRestrictingColumns(options.Condition, p.InstanceIDColumn()); err != nil {
return nil, err
}
builder := database.NewStatementBuilder(queryProjectStmt)
options.Write(builder)
return builder, nil
}
const queryGrantedProjectStmt = `SELECT
projects.instance_id,
projects.organization_id,
projects.id as id,
grants.created_at,
grants.updated_at,
projects.name,
projects.state,
projects.should_assert_role,
projects.is_authorization_required,
projects.is_project_access_required,
projects.used_labeling_setting_owner,
grants.id AS grant_id,
grants.granted_organization_id,
grants.state as grant_state
FROM zitadel.projects
INNER JOIN zitadel.project_grants as grants
ON grants.instance_id = projects.instance_id
AND grants.granting_organization_id = projects.organization_id
AND grants.project_id = projects.id`
const queryProjectsUnionStmt = `SELECT
projects.instance_id,
projects.organization_id,
projects.id as id,
projects.created_at,
projects.updated_at,
projects.name,
projects.state,
projects.should_assert_role,
projects.is_authorization_required,
projects.is_project_access_required,
projects.used_labeling_setting_owner,
projects.grant_id,
projects.granted_organization_id,
projects.grant_state
FROM (` +
queryProjectStmt +
` UNION ALL ` +
queryGrantedProjectStmt +
`) AS projects`
func (p project) prepareListQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
options := new(database.QueryOpts)
for _, opt := range opts {
opt(options)
}
if err := checkRestrictingColumns(options.Condition, p.InstanceIDColumn()); err != nil {
return nil, err
}
builder := database.NewStatementBuilder(
queryProjectsUnionStmt,
)
options.Write(builder)
return builder, nil
}