mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 06:12:16 +00:00
feat(rt): project grants (#10951)
This pull request introduces a new feature that allows adding, updating, and querying project grants The changes are primarily in the backend and include new database tables, repositories, and domain logic to support project grants. ## Changes * New `project_grants` table: A new table `zitadel.project_grants` is introduced to store project grants It includes columns for `instance_id`, `id`, `state`, `project_id`, `granted_org_id` and `granting_org_id`. * New `ProjectGrantRepository`: A new repository `ProjectGrantRepository` is created to handle all database operations for project grants. It provides methods to `Get`, `List`, `Create`, `Update`, and `Delete`. * New `project_grant_roles` table: A new table `zitadel.project_grant_roles` is introduced to store project grant roles It includes columns for `instance_id`, `grant_id`, `key`, `project_id` and `project_org_id`. * New `ProjectGrantRoleRepository`: A new repository `ProjectGrantRoleRepository` is created to handle all database operations for project grant roles. It provides methods to `Get`, `List`, `Add`, and `Delete`. * New `project_grant_relational_projection`: A new projection `project_grant_relational_projection` is added to update the `zitadel.project_grants` and `zitadel.project_grant_roles` tables based on events. It handles all project grant events. * Updated `ProjectRepository`: The `ProjectRepository` is updated to list granted projects. # Additional Context Closes https://github.com/zitadel/zitadel/issues/10766 --------- Co-authored-by: Stefan Benz <[email protected]> Co-authored-by: Marco A. <[email protected]> Co-authored-by: Silvan <[email protected]>
This commit is contained in:
co-authored by
Stefan Benz
Marco A.
Silvan
parent
6051ce591d
commit
9274e008fe
@@ -27,6 +27,11 @@ type Project struct {
|
||||
IsAuthorizationRequired bool `json:"isAuthorizationRequired,omitempty" db:"is_authorization_required"`
|
||||
IsProjectAccessRequired bool `json:"isProjectAccessRequired,omitempty" db:"is_project_access_required"`
|
||||
UsedLabelingSettingOwner int16 `json:"usedLabelingSettingOwner,omitempty" db:"used_labeling_setting_owner"`
|
||||
|
||||
// Granted Project only filled in List
|
||||
GrantID *string `json:"grantId,omitempty" db:"grant_id"`
|
||||
GrantedOrganizationID *string `json:"grantedOrganizationId,omitempty" db:"granted_organization_id"`
|
||||
GrantState *ProjectGrantState `json:"grantState,omitempty" db:"grant_state"`
|
||||
}
|
||||
|
||||
type projectColumns interface {
|
||||
@@ -34,6 +39,8 @@ type projectColumns interface {
|
||||
InstanceIDColumn() database.Column
|
||||
// OrganizationIDColumn returns the column for the organization id field
|
||||
OrganizationIDColumn() database.Column
|
||||
// GrantedOrganizationIDColumn returns the column for the granted organization id field
|
||||
GrantedOrganizationIDColumn() database.Column
|
||||
// IDColumn returns the column for the id field.
|
||||
IDColumn() database.Column
|
||||
// CreatedAtColumn returns the column for the created at field.
|
||||
@@ -61,6 +68,8 @@ type projectConditions interface {
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// OrganizationIDCondition returns a filter on the organization id field.
|
||||
OrganizationIDCondition(organizationID string) database.Condition
|
||||
// GrantedOrganizationIDCondition returns a filter on the granted organization id field.
|
||||
GrantedOrganizationIDCondition(organizationID string) database.Condition
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(projectID string) database.Condition
|
||||
// NameCondition returns a filter on the name field.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
//go:generate enumer -type ProjectGrantState -transform lower -trimprefix ProjectGrantState -sql
|
||||
type ProjectGrantState uint8
|
||||
|
||||
const (
|
||||
ProjectGrantStateActive ProjectGrantState = iota
|
||||
ProjectGrantStateInactive
|
||||
)
|
||||
|
||||
type ProjectGrant struct {
|
||||
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
|
||||
ID string `json:"id,omitempty" db:"id"`
|
||||
ProjectID string `json:"projectId,omitempty" db:"project_id"`
|
||||
GrantedOrganizationID string `json:"grantedOrganizationId,omitempty" db:"granted_organization_id"`
|
||||
GrantingOrganizationID string `json:"grantingOrganizationId,omitempty" db:"granting_organization_id"`
|
||||
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
|
||||
State ProjectGrantState `json:"state,omitempty" db:"state"`
|
||||
RoleKeys []string `json:"roleKeys,omitempty" db:"role_keys"`
|
||||
}
|
||||
|
||||
type projectGrantColumns interface {
|
||||
// PrimaryKeyColumns returns the columns for the primary key fields
|
||||
PrimaryKeyColumns() []database.Column
|
||||
// InstanceIDColumn returns the column for the instance id field
|
||||
InstanceIDColumn() database.Column
|
||||
// IDColumn returns the column for the id field
|
||||
IDColumn() database.Column
|
||||
// ProjectIDColumn returns the column for the project id field
|
||||
ProjectIDColumn() database.Column
|
||||
// GrantingOrganizationIDColumn returns the column for the granting organization id field
|
||||
GrantingOrganizationIDColumn() database.Column
|
||||
// GrantedOrganizationIDColumn returns the column for the granted organization id field
|
||||
GrantedOrganizationIDColumn() database.Column
|
||||
// CreatedAtColumn returns the column for the created at field.
|
||||
CreatedAtColumn() database.Column
|
||||
// UpdatedAtColumn returns the column for the updated at field.
|
||||
UpdatedAtColumn() database.Column
|
||||
// StateColumn returns the column for the state field.
|
||||
StateColumn() database.Column
|
||||
}
|
||||
|
||||
type projectGrantConditions interface {
|
||||
// PrimaryKeyCondition returns a filter on the primary key fields.
|
||||
PrimaryKeyCondition(instanceID, id string) database.Condition
|
||||
// InstanceIDCondition returns a filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// IDCondition returns a filter on the id field.
|
||||
IDCondition(id string) database.Condition
|
||||
// ProjectIDCondition returns a filter on the project id field.
|
||||
ProjectIDCondition(projectID string) database.Condition
|
||||
// GrantingOrganizationIDCondition returns a filter on the granting organization id field.
|
||||
GrantingOrganizationIDCondition(grantingOrgID string) database.Condition
|
||||
// GrantedOrganizationIDCondition returns a filter on the granted organization id field.
|
||||
GrantedOrganizationIDCondition(grantedOrgID string) database.Condition
|
||||
// StateCondition returns a filter on the state field.
|
||||
StateCondition(state ProjectGrantState) database.Condition
|
||||
// RoleKeyCondition returns a filter on the role field.
|
||||
RoleKeyCondition(op database.TextOperation, role string) database.Condition
|
||||
ExistsRoleKey(cond database.Condition) database.Condition
|
||||
}
|
||||
|
||||
type projectGrantChanges interface {
|
||||
// SetUpdatedAt sets the updated at column.
|
||||
// Only use this when reducing events,
|
||||
// during regular updates the DB sets this column automatically.
|
||||
SetUpdatedAt(updatedAt time.Time) database.Change
|
||||
// SetState sets the state column.
|
||||
SetState(state ProjectGrantState) database.Change
|
||||
}
|
||||
|
||||
// ProjectGrantRepository manages project grants.
|
||||
//
|
||||
//go:generate mockgen -typed -package domainmock -destination ./mock/project_grant.mock.go . ProjectGrantRepository
|
||||
type ProjectGrantRepository interface {
|
||||
Repository
|
||||
|
||||
projectGrantColumns
|
||||
projectGrantConditions
|
||||
projectGrantChanges
|
||||
|
||||
// Get a single project grant. An error is returned if not exactly one project is found.
|
||||
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*ProjectGrant, error)
|
||||
// List projects grant. An empty list is returned if no project grants are found.
|
||||
List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*ProjectGrant, error)
|
||||
// Create a new project grant.
|
||||
Create(ctx context.Context, client database.QueryExecutor, project *ProjectGrant) error
|
||||
// Update an existing project grant.
|
||||
// The condition must include the instanceID and ID of the project grant to update.
|
||||
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, roleKeys []string, changes ...database.Change) (int64, error)
|
||||
// Delete an existing project grant.
|
||||
// The condition must include the instanceID and ID of the project grant to delete.
|
||||
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Code generated by "enumer -type ProjectGrantState -transform lower -trimprefix ProjectGrantState -sql"; DO NOT EDIT.
|
||||
|
||||
package domain
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _ProjectGrantStateName = "activeinactive"
|
||||
|
||||
var _ProjectGrantStateIndex = [...]uint8{0, 6, 14}
|
||||
|
||||
const _ProjectGrantStateLowerName = "activeinactive"
|
||||
|
||||
func (i ProjectGrantState) String() string {
|
||||
if i >= ProjectGrantState(len(_ProjectGrantStateIndex)-1) {
|
||||
return fmt.Sprintf("ProjectGrantState(%d)", i)
|
||||
}
|
||||
return _ProjectGrantStateName[_ProjectGrantStateIndex[i]:_ProjectGrantStateIndex[i+1]]
|
||||
}
|
||||
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
func _ProjectGrantStateNoOp() {
|
||||
var x [1]struct{}
|
||||
_ = x[ProjectGrantStateActive-(0)]
|
||||
_ = x[ProjectGrantStateInactive-(1)]
|
||||
}
|
||||
|
||||
var _ProjectGrantStateValues = []ProjectGrantState{ProjectGrantStateActive, ProjectGrantStateInactive}
|
||||
|
||||
var _ProjectGrantStateNameToValueMap = map[string]ProjectGrantState{
|
||||
_ProjectGrantStateName[0:6]: ProjectGrantStateActive,
|
||||
_ProjectGrantStateLowerName[0:6]: ProjectGrantStateActive,
|
||||
_ProjectGrantStateName[6:14]: ProjectGrantStateInactive,
|
||||
_ProjectGrantStateLowerName[6:14]: ProjectGrantStateInactive,
|
||||
}
|
||||
|
||||
var _ProjectGrantStateNames = []string{
|
||||
_ProjectGrantStateName[0:6],
|
||||
_ProjectGrantStateName[6:14],
|
||||
}
|
||||
|
||||
// ProjectGrantStateString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func ProjectGrantStateString(s string) (ProjectGrantState, error) {
|
||||
if val, ok := _ProjectGrantStateNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := _ProjectGrantStateNameToValueMap[strings.ToLower(s)]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to ProjectGrantState values", s)
|
||||
}
|
||||
|
||||
// ProjectGrantStateValues returns all values of the enum
|
||||
func ProjectGrantStateValues() []ProjectGrantState {
|
||||
return _ProjectGrantStateValues
|
||||
}
|
||||
|
||||
// ProjectGrantStateStrings returns a slice of all String values of the enum
|
||||
func ProjectGrantStateStrings() []string {
|
||||
strs := make([]string, len(_ProjectGrantStateNames))
|
||||
copy(strs, _ProjectGrantStateNames)
|
||||
return strs
|
||||
}
|
||||
|
||||
// IsAProjectGrantState returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i ProjectGrantState) IsAProjectGrantState() bool {
|
||||
for _, v := range _ProjectGrantStateValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i ProjectGrantState) Value() (driver.Value, error) {
|
||||
return i.String(), nil
|
||||
}
|
||||
|
||||
func (i *ProjectGrantState) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var str string
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
str = string(v)
|
||||
case string:
|
||||
str = v
|
||||
case fmt.Stringer:
|
||||
str = v.String()
|
||||
default:
|
||||
return fmt.Errorf("invalid value of ProjectGrantState: %[1]T(%[1]v)", value)
|
||||
}
|
||||
|
||||
val, err := ProjectGrantStateString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*i = val
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package migration
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 008_project_grants_table/up.sql
|
||||
up008ProjectGrantsTable string
|
||||
//go:embed 008_project_grants_table/down.sql
|
||||
down008ProjectGrantsTable string
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerSQLMigration(8, up008ProjectGrantsTable, down008ProjectGrantsTable)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
DROP TYPE zitadel.project_grant_state;
|
||||
DROP TABLE IF EXISTS zitadel.project_grant_roles;
|
||||
DROP TABLE IF EXISTS zitadel.project_grants;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
CREATE TYPE zitadel.project_grant_state AS ENUM (
|
||||
'active',
|
||||
'inactive'
|
||||
);
|
||||
|
||||
CREATE TABLE zitadel.project_grants(
|
||||
instance_id TEXT NOT NULL
|
||||
, id TEXT NOT NULL CHECK (id <> '')
|
||||
|
||||
, granting_organization_id TEXT NOT NULL
|
||||
, project_id TEXT NOT NULL
|
||||
, granted_organization_id TEXT NOT NULL
|
||||
|
||||
, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
|
||||
, state zitadel.project_grant_state NOT NULL
|
||||
|
||||
, PRIMARY KEY (instance_id, id)
|
||||
|
||||
, FOREIGN KEY (instance_id, granting_organization_id) REFERENCES zitadel.organizations(instance_id, id) ON DELETE CASCADE
|
||||
, FOREIGN KEY (instance_id, granted_organization_id) REFERENCES zitadel.organizations(instance_id, id) ON DELETE CASCADE
|
||||
, FOREIGN KEY (instance_id, project_id) REFERENCES zitadel.projects(instance_id, id) ON DELETE CASCADE
|
||||
|
||||
, UNIQUE (instance_id, project_id, granted_organization_id)
|
||||
);
|
||||
|
||||
CREATE TRIGGER trg_set_updated_at_project_grants
|
||||
BEFORE UPDATE ON zitadel.project_grants
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.updated_at IS NULL)
|
||||
EXECUTE FUNCTION zitadel.set_updated_at();
|
||||
|
||||
CREATE TABLE zitadel.project_grant_roles(
|
||||
instance_id TEXT NOT NULL
|
||||
, grant_id TEXT NOT NULL
|
||||
, key TEXT NOT NULL CHECK (key <> '')
|
||||
|
||||
, project_id TEXT NOT NULL
|
||||
|
||||
, PRIMARY KEY (instance_id, grant_id, key)
|
||||
|
||||
, FOREIGN KEY (instance_id, grant_id) REFERENCES zitadel.project_grants(instance_id, id) ON DELETE CASCADE
|
||||
, FOREIGN KEY (instance_id, project_id, key) REFERENCES zitadel.project_roles(instance_id, project_id, key) ON DELETE CASCADE
|
||||
);
|
||||
@@ -0,0 +1,154 @@
|
||||
//go:build integration
|
||||
|
||||
package events_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
v2beta_project "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
|
||||
)
|
||||
|
||||
func TestServer_ProjectGrantReduces(t *testing.T) {
|
||||
instanceID := Instance.ID()
|
||||
orgID := Instance.DefaultOrg.Id
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
projectName := integration.ProjectName()
|
||||
createProjectRes, err := ProjectClient.CreateProject(CTX, &v2beta_project.CreateProjectRequest{
|
||||
OrganizationId: orgID,
|
||||
Name: projectName,
|
||||
ProjectRoleAssertion: true,
|
||||
AuthorizationRequired: true,
|
||||
ProjectAccessRequired: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
keyName := "key"
|
||||
_, err = ProjectClient.AddProjectRole(CTX, &v2beta_project.AddProjectRoleRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
RoleKey: keyName,
|
||||
DisplayName: "display",
|
||||
Group: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
grantedOrgRes, err := OrgClient.CreateOrganization(CTX, &org.CreateOrganizationRequest{
|
||||
Name: integration.OrganizationName(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ProjectClient.CreateProjectGrant(CTX, &v2beta_project.CreateProjectGrantRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
GrantedOrganizationId: grantedOrgRes.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||
|
||||
t.Run("create project grant reduces", func(t *testing.T) {
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbProjectGrant, err := projectGrantRepo.Get(CTX, pool, database.WithCondition(
|
||||
database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ProjectIDCondition(createProjectRes.GetId()),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(grantedOrgRes.GetId()),
|
||||
),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assert.Equal(collect, createProjectRes.GetId(), dbProjectGrant.ProjectID)
|
||||
assert.Equal(collect, orgID, dbProjectGrant.GrantingOrganizationID)
|
||||
assert.Equal(collect, grantedOrgRes.GetId(), dbProjectGrant.GrantedOrganizationID)
|
||||
assert.Equal(collect, domain.ProjectGrantStateActive, dbProjectGrant.State)
|
||||
assert.NotNil(collect, dbProjectGrant.CreatedAt)
|
||||
assert.NotNil(collect, dbProjectGrant.UpdatedAt)
|
||||
}, retryDuration, tick, "project grant not found within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("update project grant reduces", func(t *testing.T) {
|
||||
_, err := ProjectClient.UpdateProjectGrant(CTX, &v2beta_project.UpdateProjectGrantRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
GrantedOrganizationId: grantedOrgRes.GetId(),
|
||||
RoleKeys: []string{keyName},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbProjectGrant, err := projectGrantRepo.Get(CTX, pool, database.WithCondition(
|
||||
database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ProjectIDCondition(createProjectRes.GetId()),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(grantedOrgRes.GetId()),
|
||||
),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
if !assert.Len(collect, dbProjectGrant.RoleKeys, 1) {
|
||||
return
|
||||
}
|
||||
assert.Equal(collect, dbProjectGrant.RoleKeys[0], keyName)
|
||||
}, retryDuration, tick, "project grant not updated within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("(de)activate project grant reduces", func(t *testing.T) {
|
||||
_, err := ProjectClient.DeactivateProjectGrant(CTX, &v2beta_project.DeactivateProjectGrantRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
GrantedOrganizationId: grantedOrgRes.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbProject, err := projectGrantRepo.Get(CTX, pool, database.WithCondition(
|
||||
database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ProjectIDCondition(createProjectRes.GetId()),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(grantedOrgRes.GetId()),
|
||||
),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assert.Equal(collect, domain.ProjectGrantStateInactive, dbProject.State)
|
||||
}, retryDuration, tick, "project grant not deactivated within %v: %v", retryDuration, err)
|
||||
|
||||
_, err = ProjectClient.ActivateProjectGrant(CTX, &v2beta_project.ActivateProjectGrantRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
GrantedOrganizationId: grantedOrgRes.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
dbProject, err := projectGrantRepo.Get(CTX, pool, database.WithCondition(
|
||||
database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ProjectIDCondition(createProjectRes.GetId()),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(grantedOrgRes.GetId()),
|
||||
),
|
||||
))
|
||||
require.NoError(collect, err)
|
||||
assert.Equal(collect, domain.ProjectGrantStateActive, dbProject.State)
|
||||
}, retryDuration, tick, "project grant not activated within %v: %v", retryDuration, err)
|
||||
})
|
||||
|
||||
t.Run("delete project grant reduces", func(t *testing.T) {
|
||||
_, err := ProjectClient.DeleteProjectGrant(CTX, &v2beta_project.DeleteProjectGrantRequest{
|
||||
ProjectId: createProjectRes.GetId(),
|
||||
GrantedOrganizationId: grantedOrgRes.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
_, err := projectGrantRepo.Get(CTX, pool, database.WithCondition(
|
||||
database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ProjectIDCondition(createProjectRes.GetId()),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(grantedOrgRes.GetId()),
|
||||
),
|
||||
))
|
||||
require.ErrorIs(collect, err, database.NewNoRowFoundError(nil))
|
||||
}, retryDuration, tick, "project grant not deleted within %v: %v", retryDuration, err)
|
||||
})
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func (project) Role() domain.ProjectRoleRepository {
|
||||
}
|
||||
|
||||
func (p project) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.Project, error) {
|
||||
builder, err := p.prepareQuery(opts)
|
||||
builder, err := p.prepareGetQuery(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (p project) Get(ctx context.Context, client database.QueryExecutor, opts ..
|
||||
}
|
||||
|
||||
func (p project) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.Project, error) {
|
||||
builder, err := p.prepareQuery(opts)
|
||||
builder, err := p.prepareListQuery(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -62,31 +62,11 @@ func (project) Create(ctx context.Context, client database.QueryExecutor, projec
|
||||
}
|
||||
|
||||
func (p project) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error) {
|
||||
if len(changes) == 0 {
|
||||
return 0, database.ErrNoChanges
|
||||
}
|
||||
if err := checkPKCondition(p, condition); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !database.Changes(changes).IsOnColumn(p.UpdatedAtColumn()) {
|
||||
changes = append(changes, database.NewChange(p.UpdatedAtColumn(), database.NullInstruction))
|
||||
}
|
||||
builder := database.NewStatementBuilder(`UPDATE zitadel.projects SET `)
|
||||
database.Changes(changes).Write(builder)
|
||||
writeCondition(builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
return updateOne(ctx, client, p, condition, changes...)
|
||||
}
|
||||
|
||||
func (p project) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
|
||||
if err := checkPKCondition(p, condition); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
builder := database.NewStatementBuilder(`DELETE FROM zitadel.projects `)
|
||||
writeCondition(builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
return deleteOne(ctx, client, p, condition)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
@@ -140,6 +120,10 @@ func (p project) OrganizationIDCondition(organizationID string) database.Conditi
|
||||
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)
|
||||
}
|
||||
@@ -156,6 +140,10 @@ func (p project) StateCondition(state domain.ProjectState) database.Condition {
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (p project) qualifiedTableName() string {
|
||||
return "zitadel." + p.unqualifiedTableName()
|
||||
}
|
||||
|
||||
func (project) unqualifiedTableName() string {
|
||||
return "projects"
|
||||
}
|
||||
@@ -176,6 +164,10 @@ 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")
|
||||
}
|
||||
@@ -227,10 +219,13 @@ const queryProjectStmt = `SELECT
|
||||
projects.should_assert_role,
|
||||
projects.is_authorization_required,
|
||||
projects.is_project_access_required,
|
||||
projects.used_labeling_setting_owner
|
||||
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) prepareQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
|
||||
func (p project) prepareGetQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
@@ -243,3 +238,61 @@ func (p project) prepareQuery(opts []database.QueryOption) (*database.StatementB
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
type projectGrant struct{}
|
||||
|
||||
// ProjectGrantRepository manages project grants.
|
||||
func ProjectGrantRepository() domain.ProjectGrantRepository {
|
||||
return new(projectGrant)
|
||||
}
|
||||
|
||||
func (p projectGrant) Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*domain.ProjectGrant, error) {
|
||||
opts = append(opts,
|
||||
database.WithGroupBy(p.InstanceIDColumn(), p.IDColumn()),
|
||||
)
|
||||
|
||||
builder, err := p.prepareQuery(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getOne[domain.ProjectGrant](ctx, client, builder)
|
||||
}
|
||||
|
||||
func (p projectGrant) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.ProjectGrant, error) {
|
||||
opts = append(opts,
|
||||
database.WithGroupBy(p.InstanceIDColumn(), p.IDColumn()),
|
||||
)
|
||||
|
||||
builder, err := p.prepareQuery(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getMany[domain.ProjectGrant](ctx, client, builder)
|
||||
}
|
||||
|
||||
const insertProjectGrantRolesStmt = `WITH added_roles AS (
|
||||
INSERT INTO zitadel.project_grant_roles (
|
||||
instance_id, grant_id, project_id, key
|
||||
)
|
||||
VALUES ($2, $3, $4, unnest($1::text[]))
|
||||
) `
|
||||
|
||||
func (p projectGrant) Create(ctx context.Context, client database.QueryExecutor, projectGrant *domain.ProjectGrant) error {
|
||||
var (
|
||||
createdAt, updatedAt any = database.DefaultInstruction, database.DefaultInstruction
|
||||
)
|
||||
if !projectGrant.CreatedAt.IsZero() {
|
||||
createdAt = projectGrant.CreatedAt
|
||||
}
|
||||
if !projectGrant.UpdatedAt.IsZero() {
|
||||
updatedAt = projectGrant.UpdatedAt
|
||||
}
|
||||
|
||||
// separate statement to add roles to project grant
|
||||
builder := database.NewStatementBuilder(insertProjectGrantRolesStmt, projectGrant.RoleKeys)
|
||||
|
||||
builder.WriteString(`INSERT INTO ` + p.qualifiedTableName() + ` (instance_id, id, project_id, granting_organization_id, granted_organization_id, state, created_at, updated_at) VALUES ( `)
|
||||
builder.WriteArgs(
|
||||
projectGrant.InstanceID,
|
||||
projectGrant.ID,
|
||||
projectGrant.ProjectID,
|
||||
projectGrant.GrantingOrganizationID,
|
||||
projectGrant.GrantedOrganizationID,
|
||||
projectGrant.State,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
)
|
||||
builder.WriteString(` ) RETURNING created_at, updated_at`)
|
||||
|
||||
return client.QueryRow(ctx, builder.String(), builder.Args()...).
|
||||
Scan(&projectGrant.CreatedAt, &projectGrant.UpdatedAt)
|
||||
}
|
||||
|
||||
const queryUpdateProjectGrantRoleStmt = `SELECT instance_id, id, project_id, $1::text[] as keys from zitadel.project_grants`
|
||||
|
||||
const updateProjectGrantRoleStmt = `removed_roles AS (
|
||||
DELETE FROM zitadel.project_grant_roles as pgr
|
||||
USING pg
|
||||
WHERE pg.instance_id = pgr.instance_id AND pg.id = pgr.grant_id AND NOT(pgr.key = ANY(pg.keys))
|
||||
), added_roles AS (
|
||||
INSERT INTO zitadel.project_grant_roles (
|
||||
instance_id, grant_id, project_id, key
|
||||
)
|
||||
SELECT instance_id, id, project_id, unnest(pg.keys) FROM pg
|
||||
ON CONFLICT (instance_id, grant_id, key) DO NOTHING
|
||||
)
|
||||
UPDATE zitadel.project_grants SET `
|
||||
|
||||
const updateProjectGrantRoleStmtWhere = ` FROM pg
|
||||
WHERE pg.instance_id = project_grants.instance_id
|
||||
AND pg.id = project_grants.id`
|
||||
|
||||
func (p projectGrant) Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, roleKeys []string, changes ...database.Change) (int64, error) {
|
||||
// if no role keys set we only have to update the project grant table
|
||||
if roleKeys == nil {
|
||||
return updateOne(ctx, client, p, condition, changes...)
|
||||
}
|
||||
|
||||
// if you want to update the roles you have to have the primary key, otherwise multiple project grants get updated
|
||||
if err := checkPKCondition(p, condition); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !database.Changes(changes).IsOnColumn(p.UpdatedAtColumn()) {
|
||||
changes = append(changes, database.NewChange(p.UpdatedAtColumn(), database.NullInstruction))
|
||||
}
|
||||
|
||||
// the statement begins with getting the project grant we want to update
|
||||
builder := database.NewStatementBuilder("WITH pg AS (")
|
||||
builder.WriteString(queryUpdateProjectGrantRoleStmt)
|
||||
builder.AppendArg(roleKeys)
|
||||
writeCondition(builder, condition)
|
||||
builder.WriteString(" ), ")
|
||||
|
||||
// now we add the logic to do the required changes based on the given project grant
|
||||
builder.WriteString(updateProjectGrantRoleStmt)
|
||||
database.Changes(changes).Write(builder)
|
||||
builder.WriteString(updateProjectGrantRoleStmtWhere)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
func (p projectGrant) Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error) {
|
||||
return deleteOne(ctx, client, p, condition)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (p projectGrant) SetUpdatedAt(updatedAt time.Time) database.Change {
|
||||
return database.NewChange(p.UpdatedAtColumn(), updatedAt)
|
||||
}
|
||||
|
||||
func (p projectGrant) SetState(state domain.ProjectGrantState) database.Change {
|
||||
return database.NewChange(p.StateColumn(), state)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (p projectGrant) PrimaryKeyCondition(instanceID, projectGrantID string) database.Condition {
|
||||
return database.And(
|
||||
p.InstanceIDCondition(instanceID),
|
||||
p.IDCondition(projectGrantID),
|
||||
)
|
||||
}
|
||||
|
||||
func (p projectGrant) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(p.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
func (p projectGrant) OrganizationCondition(organizationID string) database.Condition {
|
||||
return database.Or(
|
||||
p.GrantingOrganizationIDCondition(organizationID),
|
||||
p.GrantedOrganizationIDCondition(organizationID),
|
||||
)
|
||||
}
|
||||
|
||||
func (p projectGrant) IDCondition(projectGrantID string) database.Condition {
|
||||
return database.NewTextCondition(p.IDColumn(), database.TextOperationEqual, projectGrantID)
|
||||
}
|
||||
|
||||
func (p projectGrant) ProjectIDCondition(projectID string) database.Condition {
|
||||
return database.NewTextCondition(p.ProjectIDColumn(), database.TextOperationEqual, projectID)
|
||||
}
|
||||
|
||||
func (p projectGrant) GrantingOrganizationIDCondition(organizationID string) database.Condition {
|
||||
return database.NewTextCondition(p.GrantingOrganizationIDColumn(), database.TextOperationEqual, organizationID)
|
||||
}
|
||||
|
||||
func (p projectGrant) GrantedOrganizationIDCondition(organizationID string) database.Condition {
|
||||
return database.NewTextCondition(p.GrantedOrganizationIDColumn(), database.TextOperationEqual, organizationID)
|
||||
}
|
||||
|
||||
func (p projectGrant) StateCondition(state domain.ProjectGrantState) database.Condition {
|
||||
return database.NewTextCondition(p.StateColumn(), database.TextOperationEqual, state.String())
|
||||
}
|
||||
|
||||
func (p projectGrant) RoleKeyCondition(op database.TextOperation, role string) database.Condition {
|
||||
return database.NewTextCondition(database.NewColumn(p.unqualifiedRolesTableName(), "key"), op, role)
|
||||
}
|
||||
|
||||
// ExistsRoleKey creates a correlated [database.Exists] condition on project_grant_roles.
|
||||
// Use this filter to make sure the project grant returned contains a specific project grant role.
|
||||
// Example usage:
|
||||
//
|
||||
// projectGrant, _ := projectGrantRepo.Get(ctx,
|
||||
// database.WithCondition(
|
||||
// database.And(
|
||||
// projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
// projectGrantRepo.ExistsRoleKey(projectGrantRepo.RoleKeyCondition(database.TextOperationEqual, "admin")),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
func (p projectGrant) ExistsRoleKey(cond database.Condition) database.Condition {
|
||||
return database.Exists(
|
||||
p.qualifiedRolesTableName(),
|
||||
database.And(
|
||||
database.NewColumnCondition(p.InstanceIDColumn(), database.NewColumn(p.unqualifiedRolesTableName(), "instance_id")),
|
||||
database.NewColumnCondition(p.IDColumn(), database.NewColumn(p.unqualifiedRolesTableName(), "grant_id")),
|
||||
cond,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
func (p projectGrant) qualifiedTableName() string {
|
||||
return "zitadel." + p.unqualifiedTableName()
|
||||
}
|
||||
|
||||
func (projectGrant) unqualifiedTableName() string {
|
||||
return "project_grants"
|
||||
}
|
||||
|
||||
func (p projectGrant) qualifiedRolesTableName() string {
|
||||
return "zitadel." + p.unqualifiedRolesTableName()
|
||||
}
|
||||
|
||||
func (projectGrant) unqualifiedRolesTableName() string {
|
||||
return "project_grant_roles"
|
||||
}
|
||||
|
||||
func (p projectGrant) PrimaryKeyColumns() []database.Column {
|
||||
return []database.Column{
|
||||
p.InstanceIDColumn(),
|
||||
p.IDColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p projectGrant) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "instance_id")
|
||||
}
|
||||
|
||||
func (p projectGrant) IDColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "id")
|
||||
}
|
||||
|
||||
func (p projectGrant) ProjectIDColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "project_id")
|
||||
}
|
||||
|
||||
func (p projectGrant) GrantingOrganizationIDColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "granting_organization_id")
|
||||
}
|
||||
|
||||
func (p projectGrant) GrantedOrganizationIDColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "granted_organization_id")
|
||||
}
|
||||
|
||||
func (p projectGrant) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "created_at")
|
||||
}
|
||||
|
||||
func (p projectGrant) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "updated_at")
|
||||
}
|
||||
|
||||
func (p projectGrant) StateColumn() database.Column {
|
||||
return database.NewColumn(p.unqualifiedTableName(), "state")
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// helpers
|
||||
// -------------------------------------------------------------
|
||||
|
||||
const queryProjectGrantStmt = `SELECT
|
||||
zitadel.project_grants.instance_id,
|
||||
zitadel.project_grants.id,
|
||||
zitadel.project_grants.project_id,
|
||||
zitadel.project_grants.granting_organization_id,
|
||||
zitadel.project_grants.granted_organization_id,
|
||||
zitadel.project_grants.created_at,
|
||||
zitadel.project_grants.updated_at,
|
||||
zitadel.project_grants.state,
|
||||
ARRAY_AGG(project_grant_roles.key) FILTER (WHERE project_grant_roles.grant_id IS NOT NULL) AS role_keys
|
||||
FROM zitadel.project_grants
|
||||
LEFT JOIN zitadel.project_grant_roles ON zitadel.project_grant_roles.instance_id = zitadel.project_grants.instance_id AND zitadel.project_grant_roles.grant_id = zitadel.project_grants.id`
|
||||
|
||||
func (p projectGrant) prepareQuery(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(queryProjectGrantStmt)
|
||||
options.Write(builder)
|
||||
|
||||
return builder, nil
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
)
|
||||
|
||||
func TestGetProjectGrant(t *testing.T) {
|
||||
tx, rollback := transactionForRollback(t)
|
||||
defer rollback()
|
||||
instanceID := createInstance(t, tx)
|
||||
grantingOrgID := createOrganization(t, tx, instanceID)
|
||||
firstGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
secondGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectID := createProject(t, tx, instanceID, grantingOrgID)
|
||||
firstRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
secondRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
thirdRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
firstProjectGrant := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
ProjectID: projectID,
|
||||
GrantedOrganizationID: firstGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey},
|
||||
}
|
||||
err := projectGrantRepo.Create(t.Context(), tx, firstProjectGrant)
|
||||
require.NoError(t, err)
|
||||
secondProjectGrant := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
ProjectID: projectID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateInactive,
|
||||
RoleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
}
|
||||
err = projectGrantRepo.Create(t.Context(), tx, secondProjectGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition database.Condition
|
||||
want *domain.ProjectGrant
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "incomplete condition",
|
||||
condition: projectGrantRepo.IDCondition(firstProjectGrant.ID),
|
||||
wantErr: database.NewMissingConditionError(projectGrantRepo.IDColumn()),
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, "nix"),
|
||||
wantErr: database.NewNoRowFoundError(nil),
|
||||
},
|
||||
{
|
||||
name: "too many",
|
||||
condition: projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
wantErr: database.NewMultipleRowsFoundError(nil),
|
||||
},
|
||||
{
|
||||
name: "ok first",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, firstProjectGrant.ID),
|
||||
want: firstProjectGrant,
|
||||
},
|
||||
{
|
||||
name: "ok second",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, secondProjectGrant.ID),
|
||||
want: secondProjectGrant,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := projectGrantRepo.Get(t.Context(), tx, database.WithCondition(tt.condition))
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assertProjectGrant(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProjectGrants(t *testing.T) {
|
||||
tx, rollback := transactionForRollback(t)
|
||||
defer rollback()
|
||||
instanceID := createInstance(t, tx)
|
||||
firstGrantingOrgID := createOrganization(t, tx, instanceID)
|
||||
firstProjectID := createProject(t, tx, instanceID, firstGrantingOrgID)
|
||||
firstRoleKey := createProjectRole(t, tx, instanceID, firstGrantingOrgID, firstProjectID, "")
|
||||
secondRoleKey := createProjectRole(t, tx, instanceID, firstGrantingOrgID, firstProjectID, "")
|
||||
thirdRoleKey := createProjectRole(t, tx, instanceID, firstGrantingOrgID, firstProjectID, "")
|
||||
secondGrantingOrgID := createOrganization(t, tx, instanceID)
|
||||
secondProjectID := createProject(t, tx, instanceID, secondGrantingOrgID)
|
||||
|
||||
firstGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
secondGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
projectGrants := [...]*domain.ProjectGrant{
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
ID: "1",
|
||||
ProjectID: firstProjectID,
|
||||
GrantedOrganizationID: firstGrantedOrgID,
|
||||
GrantingOrganizationID: firstGrantingOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey},
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
ID: "2",
|
||||
ProjectID: firstProjectID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
GrantingOrganizationID: firstGrantingOrgID,
|
||||
State: domain.ProjectGrantStateInactive,
|
||||
RoleKeys: []string{secondRoleKey, thirdRoleKey},
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
ID: "3",
|
||||
ProjectID: secondProjectID,
|
||||
GrantedOrganizationID: firstGrantedOrgID,
|
||||
GrantingOrganizationID: secondGrantingOrgID,
|
||||
State: domain.ProjectGrantStateInactive,
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
ID: "4",
|
||||
ProjectID: secondProjectID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
GrantingOrganizationID: secondGrantingOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
},
|
||||
}
|
||||
for _, projectGrant := range projectGrants {
|
||||
err := projectGrantRepo.Create(t.Context(), tx, projectGrant)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition database.Condition
|
||||
want []*domain.ProjectGrant
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "incomplete condition",
|
||||
condition: projectGrantRepo.GrantedOrganizationIDCondition(firstGrantedOrgID),
|
||||
wantErr: database.NewMissingConditionError(projectGrantRepo.IDColumn()),
|
||||
},
|
||||
{
|
||||
name: "no results, ok",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, "nix"),
|
||||
},
|
||||
{
|
||||
name: "all from instance",
|
||||
condition: projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
want: projectGrants[:],
|
||||
},
|
||||
{
|
||||
name: "all granting from first org",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.GrantingOrganizationIDCondition(firstGrantingOrgID),
|
||||
),
|
||||
want: projectGrants[0:2],
|
||||
},
|
||||
{
|
||||
name: "all granting from second org",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.GrantingOrganizationIDCondition(secondGrantingOrgID),
|
||||
),
|
||||
want: projectGrants[2:4],
|
||||
},
|
||||
{
|
||||
name: "all granted from first org",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.GrantingOrganizationIDCondition(firstGrantingOrgID),
|
||||
),
|
||||
want: []*domain.ProjectGrant{projectGrants[0], projectGrants[1]},
|
||||
},
|
||||
{
|
||||
name: "all granted to second org",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.GrantedOrganizationIDCondition(secondGrantedOrgID),
|
||||
),
|
||||
want: []*domain.ProjectGrant{projectGrants[1], projectGrants[3]},
|
||||
},
|
||||
{
|
||||
name: "state active",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.StateCondition(domain.ProjectGrantStateActive),
|
||||
),
|
||||
want: []*domain.ProjectGrant{projectGrants[0], projectGrants[3]},
|
||||
},
|
||||
{
|
||||
name: "exists role key",
|
||||
condition: database.And(
|
||||
projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
projectGrantRepo.ExistsRoleKey(projectGrantRepo.RoleKeyCondition(database.TextOperationEqual, firstRoleKey)),
|
||||
),
|
||||
want: []*domain.ProjectGrant{projectGrants[0]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := projectGrantRepo.List(t.Context(), tx,
|
||||
database.WithCondition(tt.condition),
|
||||
database.WithOrderByAscending(projectGrantRepo.PrimaryKeyColumns()...),
|
||||
)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
|
||||
if !assert.Len(t, got, len(tt.want)) {
|
||||
return
|
||||
}
|
||||
for i, want := range tt.want {
|
||||
assertProjectGrant(t, want, got[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProjectGrant(t *testing.T) {
|
||||
beforeCreate := time.Now()
|
||||
|
||||
tx, rollback := transactionForRollback(t)
|
||||
defer rollback()
|
||||
instanceID := createInstance(t, tx)
|
||||
grantingOrgID := createOrganization(t, tx, instanceID)
|
||||
firstGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
secondGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
thirdGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
fourthGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectID := createProject(t, tx, instanceID, grantingOrgID)
|
||||
firstRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
secondRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
thirdRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
existingProjectGrant := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: firstGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
}
|
||||
err := projectGrantRepo.Create(t.Context(), tx, existingProjectGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
projectGrant *domain.ProjectGrant
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "add project grant",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-existing instance",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: "foo",
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
},
|
||||
wantErr: new(database.ForeignKeyError),
|
||||
},
|
||||
{
|
||||
name: "non-existing org",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: "foo",
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
},
|
||||
wantErr: new(database.ForeignKeyError),
|
||||
},
|
||||
{
|
||||
name: "empty id error",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: "",
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
},
|
||||
wantErr: new(database.CheckError),
|
||||
},
|
||||
{
|
||||
name: "duplicate project grant",
|
||||
projectGrant: existingProjectGrant,
|
||||
wantErr: new(database.UniqueError),
|
||||
},
|
||||
{
|
||||
name: "add project grant with role",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: thirdGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add project grant with multiple roles",
|
||||
projectGrant: &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: fourthGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
savepoint, rollback := savepointForRollback(t, tx)
|
||||
defer rollback()
|
||||
err := projectGrantRepo.Create(t.Context(), savepoint, tt.projectGrant)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
afterCreate := time.Now()
|
||||
|
||||
// check instance values
|
||||
projectGrant, err := projectGrantRepo.Get(t.Context(), tx,
|
||||
database.WithCondition(
|
||||
projectGrantRepo.PrimaryKeyCondition(tt.projectGrant.InstanceID, tt.projectGrant.ID),
|
||||
),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assertProjectGrant(t, tt.projectGrant, projectGrant)
|
||||
assert.WithinRange(t, projectGrant.CreatedAt, beforeCreate, afterCreate)
|
||||
assert.WithinRange(t, projectGrant.UpdatedAt, beforeCreate, afterCreate)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertProjectGrant(t assert.TestingT, expected *domain.ProjectGrant, actual *domain.ProjectGrant) {
|
||||
if expected == nil {
|
||||
assert.Nil(t, actual)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, expected.ID, actual.ID)
|
||||
assert.Equal(t, expected.ProjectID, actual.ProjectID)
|
||||
assert.Equal(t, expected.GrantingOrganizationID, actual.GrantingOrganizationID)
|
||||
assert.Equal(t, expected.GrantedOrganizationID, actual.GrantedOrganizationID)
|
||||
assert.Equal(t, expected.State, actual.State)
|
||||
assert.ElementsMatch(t, expected.RoleKeys, actual.RoleKeys)
|
||||
}
|
||||
|
||||
func TestUpdateProjectGrant(t *testing.T) {
|
||||
tx, rollback := transactionForRollback(t)
|
||||
defer rollback()
|
||||
instanceID := createInstance(t, tx)
|
||||
grantingOrgID := createOrganization(t, tx, instanceID)
|
||||
firstGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
secondGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
thirdGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
fourthGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
fifthGrantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectID := createProject(t, tx, instanceID, grantingOrgID)
|
||||
firstRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
secondRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
thirdRoleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
existingProjectGrant := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: firstGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
}
|
||||
err := projectGrantRepo.Create(t.Context(), tx, existingProjectGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
existingProjectGrantWithRole := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: secondGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey},
|
||||
}
|
||||
err = projectGrantRepo.Create(t.Context(), tx, existingProjectGrantWithRole)
|
||||
require.NoError(t, err)
|
||||
|
||||
existingProjectGrantWithRoles := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: thirdGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
}
|
||||
err = projectGrantRepo.Create(t.Context(), tx, existingProjectGrantWithRoles)
|
||||
require.NoError(t, err)
|
||||
|
||||
existingProjectGrantWithAllRoles := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: fourthGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
}
|
||||
err = projectGrantRepo.Create(t.Context(), tx, existingProjectGrantWithAllRoles)
|
||||
require.NoError(t, err)
|
||||
|
||||
existingProjectGrantToRemoveRoles := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: fifthGrantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
}
|
||||
err = projectGrantRepo.Create(t.Context(), tx, existingProjectGrantToRemoveRoles)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition database.Condition
|
||||
roleKeys []string
|
||||
changes []database.Change
|
||||
wantRowsAffected int64
|
||||
wantErr error
|
||||
assertChanges func(t *testing.T, project *domain.ProjectGrant)
|
||||
}{
|
||||
{
|
||||
name: "no changes",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrant.ID),
|
||||
changes: []database.Change{},
|
||||
wantRowsAffected: 0,
|
||||
wantErr: database.ErrNoChanges,
|
||||
},
|
||||
{
|
||||
name: "incomplete condition",
|
||||
condition: projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
changes: []database.Change{
|
||||
projectGrantRepo.SetState(domain.ProjectGrantStateActive),
|
||||
},
|
||||
wantRowsAffected: 0,
|
||||
wantErr: database.NewMissingConditionError(projectGrantRepo.IDColumn()),
|
||||
},
|
||||
{
|
||||
name: "set state",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrant.ID),
|
||||
changes: []database.Change{
|
||||
projectGrantRepo.SetState(domain.ProjectGrantStateInactive),
|
||||
},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProject *domain.ProjectGrant) {
|
||||
assert.Equal(t, domain.ProjectGrantStateInactive, updatedProject.State)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "incomplete condition for set rolekey",
|
||||
condition: projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
roleKeys: []string{firstRoleKey},
|
||||
changes: []database.Change{
|
||||
projectGrantRepo.SetState(domain.ProjectGrantStateActive),
|
||||
},
|
||||
wantRowsAffected: 0,
|
||||
wantErr: database.NewMissingConditionError(projectGrantRepo.IDColumn()),
|
||||
},
|
||||
{
|
||||
name: "set rolekey",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrant.ID),
|
||||
roleKeys: []string{firstRoleKey},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProjectGrant *domain.ProjectGrant) {
|
||||
assert.WithinRange(t, updatedProjectGrant.CreatedAt, existingProjectGrant.CreatedAt, existingProjectGrant.CreatedAt)
|
||||
assert.WithinRange(t, updatedProjectGrant.UpdatedAt, existingProjectGrant.UpdatedAt, existingProjectGrant.UpdatedAt.Add(time.Second))
|
||||
assert.ElementsMatch(t, []string{firstRoleKey}, updatedProjectGrant.RoleKeys)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add rolekeys",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrantWithRole.ID),
|
||||
roleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProjectGrant *domain.ProjectGrant) {
|
||||
assert.WithinRange(t, updatedProjectGrant.CreatedAt, existingProjectGrantWithRole.CreatedAt, existingProjectGrantWithRole.CreatedAt)
|
||||
assert.WithinRange(t, updatedProjectGrant.UpdatedAt, existingProjectGrantWithRole.UpdatedAt, existingProjectGrantWithRole.UpdatedAt.Add(time.Second))
|
||||
assert.ElementsMatch(t, []string{firstRoleKey, secondRoleKey, thirdRoleKey}, updatedProjectGrant.RoleKeys)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove rolekeys",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrantWithRoles.ID),
|
||||
roleKeys: []string{firstRoleKey},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProjectGrant *domain.ProjectGrant) {
|
||||
assert.WithinRange(t, updatedProjectGrant.CreatedAt, existingProjectGrantWithRoles.CreatedAt, existingProjectGrantWithRoles.CreatedAt)
|
||||
assert.WithinRange(t, updatedProjectGrant.UpdatedAt, existingProjectGrantWithRoles.UpdatedAt, existingProjectGrantWithRoles.UpdatedAt.Add(time.Second))
|
||||
assert.ElementsMatch(t, []string{firstRoleKey}, updatedProjectGrant.RoleKeys)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove all rolekeys",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrantToRemoveRoles.ID),
|
||||
roleKeys: []string{},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProjectGrant *domain.ProjectGrant) {
|
||||
assert.WithinRange(t, updatedProjectGrant.CreatedAt, existingProjectGrantToRemoveRoles.CreatedAt, existingProjectGrantToRemoveRoles.CreatedAt)
|
||||
assert.WithinRange(t, updatedProjectGrant.UpdatedAt, existingProjectGrantToRemoveRoles.UpdatedAt, existingProjectGrantToRemoveRoles.UpdatedAt.Add(time.Second))
|
||||
assert.ElementsMatch(t, []string{}, updatedProjectGrant.RoleKeys)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes role keys",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrantWithAllRoles.ID),
|
||||
roleKeys: []string{firstRoleKey, secondRoleKey, thirdRoleKey},
|
||||
wantRowsAffected: 1,
|
||||
assertChanges: func(t *testing.T, updatedProjectGrant *domain.ProjectGrant) {
|
||||
assert.WithinRange(t, updatedProjectGrant.CreatedAt, existingProjectGrantWithAllRoles.CreatedAt, existingProjectGrantWithAllRoles.CreatedAt)
|
||||
assert.WithinRange(t, updatedProjectGrant.UpdatedAt, existingProjectGrantWithAllRoles.UpdatedAt, existingProjectGrantWithAllRoles.UpdatedAt.Add(time.Second))
|
||||
assert.ElementsMatch(t, []string{firstRoleKey, secondRoleKey, thirdRoleKey}, updatedProjectGrant.RoleKeys)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
savepoint, rollback := savepointForRollback(t, tx)
|
||||
defer rollback()
|
||||
rowsAffected, err := projectGrantRepo.Update(t.Context(), savepoint, tt.condition, tt.roleKeys, tt.changes...)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.wantRowsAffected, rowsAffected)
|
||||
|
||||
if tt.assertChanges != nil {
|
||||
updatedProjectGrant, err := projectGrantRepo.Get(t.Context(), savepoint, database.WithCondition(tt.condition))
|
||||
require.NoError(t, err)
|
||||
tt.assertChanges(t, updatedProjectGrant)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteProjectGrant(t *testing.T) {
|
||||
tx, rollback := transactionForRollback(t)
|
||||
defer rollback()
|
||||
instanceID := createInstance(t, tx)
|
||||
grantingOrgID := createOrganization(t, tx, instanceID)
|
||||
grantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectID := createProject(t, tx, instanceID, grantingOrgID)
|
||||
roleKey := createProjectRole(t, tx, instanceID, grantingOrgID, projectID, "")
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
existingProjectGrant := &domain.ProjectGrant{
|
||||
InstanceID: instanceID,
|
||||
ID: integration.ID(),
|
||||
ProjectID: projectID,
|
||||
GrantingOrganizationID: grantingOrgID,
|
||||
GrantedOrganizationID: grantedOrgID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
RoleKeys: []string{roleKey},
|
||||
}
|
||||
err := projectGrantRepo.Create(t.Context(), tx, existingProjectGrant)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
condition database.Condition
|
||||
wantRowsAffected int64
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "incomplete condition",
|
||||
condition: projectGrantRepo.InstanceIDCondition(instanceID),
|
||||
wantRowsAffected: 0,
|
||||
wantErr: database.NewMissingConditionError(projectGrantRepo.IDColumn()),
|
||||
},
|
||||
{
|
||||
name: "not found",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, "foo"),
|
||||
wantRowsAffected: 0,
|
||||
},
|
||||
{
|
||||
name: "delete project grant",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrant.ID),
|
||||
wantRowsAffected: 1,
|
||||
},
|
||||
{
|
||||
name: "delete project grant twice",
|
||||
condition: projectGrantRepo.PrimaryKeyCondition(instanceID, existingProjectGrant.ID),
|
||||
wantRowsAffected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rowsAffected, err := projectGrantRepo.Delete(t.Context(), tx, tt.condition)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.wantRowsAffected, rowsAffected)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -89,7 +90,10 @@ func TestListProjects(t *testing.T) {
|
||||
instanceID := createInstance(t, tx)
|
||||
firstOrgID := createOrganization(t, tx, instanceID)
|
||||
secondOrgID := createOrganization(t, tx, instanceID)
|
||||
thirdOrgID := createOrganization(t, tx, instanceID)
|
||||
grantedOrgID := createOrganization(t, tx, instanceID)
|
||||
projectRepo := repository.ProjectRepository()
|
||||
projectGrantRepo := repository.ProjectGrantRepository()
|
||||
|
||||
projects := [...]*domain.Project{
|
||||
{
|
||||
@@ -136,10 +140,59 @@ func TestListProjects(t *testing.T) {
|
||||
IsProjectAccessRequired: true,
|
||||
UsedLabelingSettingOwner: 1,
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: thirdOrgID,
|
||||
ID: "5",
|
||||
Name: "granted",
|
||||
State: domain.ProjectStateActive,
|
||||
ShouldAssertRole: true,
|
||||
IsAuthorizationRequired: true,
|
||||
IsProjectAccessRequired: true,
|
||||
UsedLabelingSettingOwner: 1,
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: thirdOrgID,
|
||||
ID: "5",
|
||||
Name: "granted",
|
||||
State: domain.ProjectStateActive,
|
||||
ShouldAssertRole: true,
|
||||
IsAuthorizationRequired: true,
|
||||
IsProjectAccessRequired: true,
|
||||
UsedLabelingSettingOwner: 1,
|
||||
GrantID: gu.Ptr("1"),
|
||||
GrantedOrganizationID: gu.Ptr(grantedOrgID),
|
||||
GrantState: gu.Ptr(domain.ProjectGrantStateActive),
|
||||
},
|
||||
{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: grantedOrgID,
|
||||
ID: "6",
|
||||
Name: "owned",
|
||||
State: domain.ProjectStateActive,
|
||||
ShouldAssertRole: true,
|
||||
IsAuthorizationRequired: true,
|
||||
IsProjectAccessRequired: true,
|
||||
UsedLabelingSettingOwner: 1,
|
||||
},
|
||||
}
|
||||
for _, project := range projects {
|
||||
err := projectRepo.Create(t.Context(), tx, project)
|
||||
require.NoError(t, err)
|
||||
// Granted Projects
|
||||
if project.GrantedOrganizationID != nil {
|
||||
err := projectGrantRepo.Create(t.Context(), tx, &domain.ProjectGrant{
|
||||
InstanceID: project.InstanceID,
|
||||
ID: *project.GrantID,
|
||||
ProjectID: project.ID,
|
||||
GrantedOrganizationID: *project.GrantedOrganizationID,
|
||||
GrantingOrganizationID: project.OrganizationID,
|
||||
State: domain.ProjectGrantStateActive,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
err := projectRepo.Create(t.Context(), tx, project)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -179,13 +232,28 @@ func TestListProjects(t *testing.T) {
|
||||
want: projects[1:3],
|
||||
},
|
||||
{
|
||||
name: "state active",
|
||||
name: "state active from first or second org",
|
||||
condition: database.And(
|
||||
projectRepo.InstanceIDCondition(instanceID),
|
||||
database.Or(
|
||||
projectRepo.OrganizationIDCondition(firstOrgID),
|
||||
projectRepo.OrganizationIDCondition(secondOrgID),
|
||||
),
|
||||
projectRepo.StateCondition(domain.ProjectStateActive),
|
||||
),
|
||||
want: []*domain.Project{projects[0], projects[2]},
|
||||
},
|
||||
{
|
||||
name: "all from granted org or granted to granted org",
|
||||
condition: database.And(
|
||||
projectRepo.InstanceIDCondition(instanceID),
|
||||
database.Or(
|
||||
projectRepo.OrganizationIDCondition(grantedOrgID),
|
||||
projectRepo.GrantedOrganizationIDCondition(grantedOrgID),
|
||||
),
|
||||
),
|
||||
want: []*domain.Project{projects[5], projects[6]},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -195,7 +263,7 @@ func TestListProjects(t *testing.T) {
|
||||
database.WithOrderByAscending(projectRepo.PrimaryKeyColumns()...),
|
||||
)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
assert.EqualExportedValues(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,3 +65,46 @@ func getMany[Target any](ctx context.Context, querier database.Querier, builder
|
||||
}
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
type updatable interface {
|
||||
PrimaryKeyColumns() []database.Column
|
||||
UpdatedAtColumn() database.Column
|
||||
qualifiedTableName() string
|
||||
}
|
||||
|
||||
func updateOne[Target updatable](ctx context.Context, client database.QueryExecutor, target Target, condition database.Condition, changes ...database.Change) (int64, error) {
|
||||
if len(changes) == 0 {
|
||||
return 0, database.ErrNoChanges
|
||||
}
|
||||
if err := checkPKCondition(target, condition); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !database.Changes(changes).IsOnColumn(target.UpdatedAtColumn()) {
|
||||
changes = append(changes, database.NewChange(target.UpdatedAtColumn(), database.NullInstruction))
|
||||
}
|
||||
builder := database.NewStatementBuilder("UPDATE ")
|
||||
builder.WriteString(target.qualifiedTableName())
|
||||
builder.WriteString(" SET ")
|
||||
database.Changes(changes).Write(builder)
|
||||
writeCondition(builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
type deletable interface {
|
||||
PrimaryKeyColumns() []database.Column
|
||||
qualifiedTableName() string
|
||||
}
|
||||
|
||||
func deleteOne[Target deletable](ctx context.Context, client database.QueryExecutor, target Target, condition database.Condition) (int64, error) {
|
||||
if err := checkPKCondition(target, condition); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
builder := database.NewStatementBuilder("DELETE FROM ")
|
||||
builder.WriteString(target.qualifiedTableName())
|
||||
builder.WriteRune(' ')
|
||||
writeCondition(builder, condition)
|
||||
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres/embedded"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -123,3 +124,22 @@ func createProject(t *testing.T, tx database.Transaction, instanceID, orgID stri
|
||||
|
||||
return project.ID
|
||||
}
|
||||
|
||||
func createProjectRole(t *testing.T, tx database.Transaction, instanceID, orgID, projectID, key string) string {
|
||||
t.Helper()
|
||||
if key == "" {
|
||||
key = integration.RoleKey()
|
||||
}
|
||||
projectRole := domain.ProjectRole{
|
||||
InstanceID: instanceID,
|
||||
OrganizationID: orgID,
|
||||
ProjectID: projectID,
|
||||
Key: key,
|
||||
DisplayName: integration.RoleDisplayName(),
|
||||
}
|
||||
projectRoleRepo := repository.ProjectRepository().Role()
|
||||
err := projectRoleRepo.Create(t.Context(), tx, &projectRole)
|
||||
require.NoError(t, err)
|
||||
|
||||
return projectRole.Key
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
repoDomain "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
v3_sql "github.com/zitadel/zitadel/backend/v3/storage/database/dialect/sql"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type projectGrantRelationalProjection struct{}
|
||||
|
||||
func (*projectGrantRelationalProjection) Name() string {
|
||||
return "zitadel.project_grants"
|
||||
}
|
||||
|
||||
func newProjectGrantRelationalProjection(ctx context.Context, config handler.Config) *handler.Handler {
|
||||
return handler.NewHandler(ctx, &config, new(projectGrantRelationalProjection))
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) Reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: project.AggregateType,
|
||||
EventReducers: []handler.EventReducer{
|
||||
{
|
||||
Event: project.GrantAddedType,
|
||||
Reduce: p.reduceProjectGrantAdded,
|
||||
},
|
||||
{
|
||||
Event: project.GrantChangedType,
|
||||
Reduce: p.reduceProjectGrantChanged,
|
||||
},
|
||||
{
|
||||
Event: project.GrantCascadeChangedType,
|
||||
Reduce: p.reduceProjectGrantCascadeChanged,
|
||||
},
|
||||
{
|
||||
Event: project.GrantDeactivatedType,
|
||||
Reduce: p.reduceProjectGrantDeactivated,
|
||||
},
|
||||
{
|
||||
Event: project.GrantReactivatedType,
|
||||
Reduce: p.reduceProjectGrantReactivated,
|
||||
},
|
||||
{
|
||||
Event: project.GrantRemovedType,
|
||||
Reduce: p.reduceProjectGrantRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantAddedEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInternalf(nil, "HANDL-5l2bWQrkKf", "reduce.wrong.event.type %s", project.GrantAddedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-5w96sjaQ16", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
repo := repository.ProjectGrantRepository()
|
||||
return repo.Create(ctx, v3_sql.SQLTx(tx), &repoDomain.ProjectGrant{
|
||||
InstanceID: e.Aggregate().InstanceID,
|
||||
ID: e.GrantID,
|
||||
ProjectID: e.Aggregate().ID,
|
||||
GrantedOrganizationID: e.GrantedOrgID,
|
||||
GrantingOrganizationID: e.Aggregate().ResourceOwner,
|
||||
CreatedAt: e.CreationDate(),
|
||||
UpdatedAt: e.CreationDate(),
|
||||
State: repoDomain.ProjectGrantStateActive,
|
||||
RoleKeys: e.RoleKeys,
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantChangedEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-I2ciunHcy7", "reduce.wrong.event.type %s", project.GrantChangedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-ANaKzWKAUc", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
repo := repository.ProjectGrantRepository()
|
||||
_, err := repo.Update(ctx, v3_sql.SQLTx(tx),
|
||||
repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.GrantID),
|
||||
e.RoleKeys,
|
||||
repo.SetUpdatedAt(e.CreationDate()),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantCascadeChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantCascadeChangedEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-osXtu6jnWa", "reduce.wrong.event.type %s", project.GrantCascadeChangedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-aI2o6NlWpv", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
repo := repository.ProjectGrantRepository()
|
||||
_, err := repo.Update(ctx, v3_sql.SQLTx(tx),
|
||||
repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.GrantID),
|
||||
e.RoleKeys,
|
||||
repo.SetUpdatedAt(e.CreationDate()),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantDeactivated(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantDeactivateEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-XraL17MUkr", "reduce.wrong.event.type %s", project.GrantDeactivatedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
repo := repository.ProjectGrantRepository()
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-rIjAqCzj67", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
_, err := repo.Update(ctx, v3_sql.SQLTx(tx),
|
||||
repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.GrantID),
|
||||
nil,
|
||||
repo.SetUpdatedAt(e.CreationDate()),
|
||||
repo.SetState(repoDomain.ProjectGrantStateInactive),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantReactivated(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantReactivatedEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-vGUHp6uHJ7", "reduce.wrong.event.type %s", project.GrantReactivatedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
repo := repository.ProjectGrantRepository()
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-8milgIP7BS", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
_, err := repo.Update(ctx, v3_sql.SQLTx(tx),
|
||||
repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.GrantID),
|
||||
nil,
|
||||
repo.SetUpdatedAt(e.CreationDate()),
|
||||
repo.SetState(repoDomain.ProjectGrantStateActive),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *projectGrantRelationalProjection) reduceProjectGrantRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*project.GrantRemovedEvent)
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-MVPgtdg1w5", "reduce.wrong.event.type %s", project.GrantRemovedType)
|
||||
}
|
||||
return handler.NewStatement(e, func(ctx context.Context, ex handler.Executer, _ string) error {
|
||||
repo := repository.ProjectGrantRepository()
|
||||
tx, ok := ex.(*sql.Tx)
|
||||
if !ok {
|
||||
return zerrors.ThrowInvalidArgumentf(nil, "HANDL-gYyqxBv5d0", "reduce.wrong.db.pool %T", ex)
|
||||
}
|
||||
_, err := repo.Delete(ctx, v3_sql.SQLTx(tx),
|
||||
repo.PrimaryKeyCondition(e.Aggregate().InstanceID, e.GrantID),
|
||||
)
|
||||
return err
|
||||
}), nil
|
||||
}
|
||||
@@ -97,6 +97,7 @@ var (
|
||||
ProjectRelationalProjection *handler.Handler
|
||||
ProjectRoleRelationalProjection *handler.Handler
|
||||
OrganizationMetadataRelationalProjection *handler.Handler
|
||||
ProjectGrantRelationalProjection *handler.Handler
|
||||
|
||||
ProjectGrantFields *handler.FieldHandler
|
||||
OrgDomainVerifiedFields *handler.FieldHandler
|
||||
@@ -222,6 +223,7 @@ func Create(ctx context.Context, sqlClient *database.DB, es handler.EventStore,
|
||||
ProjectRelationalProjection = newProjectRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["projects_relational"]))
|
||||
ProjectRoleRelationalProjection = newProjectRoleRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["project_roles_relational"]))
|
||||
OrganizationMetadataRelationalProjection = newOrgMetadataRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["organization_metadata_relational"]))
|
||||
ProjectGrantRelationalProjection = newProjectGrantRelationalProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["project_grant_relational"]))
|
||||
|
||||
newProjectionsList()
|
||||
newFieldsList()
|
||||
@@ -415,5 +417,6 @@ func newProjectionsList() {
|
||||
ProjectRelationalProjection,
|
||||
ProjectRoleRelationalProjection,
|
||||
OrganizationMetadataRelationalProjection,
|
||||
ProjectGrantRelationalProjection,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user