mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 03:24:26 +00:00
feat: project commands more requests (#47)
* feat: eventstore repository * fix: remove gorm * version * feat: pkg * feat: add some files for project * feat: eventstore without eventstore-lib * rename files * gnueg * fix: key json * fix: add object * fix: change imports * fix: internal models * fix: some imports * fix: global model * fix: add some functions on repo * feat(eventstore): sdk * fix(eventstore): search query * fix(eventstore): rename app to eventstore * delete empty test * remove unused func * merge master * fix(eventstore): tests * fix(models): delete unused struct * fix: some funcitons * feat(eventstore): implemented push events * fix: move project eventstore to project package * fix: change project eventstore funcs * feat(eventstore): overwrite context data * fix: change project eventstore * fix: add project repo to mgmt server * feat(types): SQL-config * fix: commented code * feat(eventstore): options to overwrite editor * feat: auth interceptor and cockroach migrations * fix: migrations * fix: fix filter * fix: not found on getbyid * fix: add sequence * fix: add some tests * fix(eventstore): nullable sequence * fix: add some tests * merge * fix: add some tests * fix(migrations): correct statements for sequence * fix: add some tests * fix: add some tests * fix: changes from mr * Update internal/eventstore/models/field.go Co-Authored-By: livio-a <livio.a@gmail.com> * fix(eventstore): code quality * fix: add types to aggregate/Event-types * fix(eventstore): rename modifier* to editor* * fix(eventstore): delete editor_org * fix(migrations): remove editor_org field, rename modifier_* to editor_* * fix: generate files * fix(eventstore): tests * fix(eventstore): rename modifier to editor * fix(migrations): add cluster migration, fix(migrations): fix typo of host in clean clsuter * fix(eventstore): move health * fix(eventstore): AggregateTypeFilter aggregateType as param * code quality * feat: add member funcs * feat: add member model * feat: add member events * feat: add member repo model * fix: project member funcs * fix: add tests * fix: add tests * feat: implement member requests * fix: merge master * fix: read existing in project repo * fix: fix tests * fix: use eventstore sdk * Update internal/project/model/project_member.go Co-Authored-By: Silvan <silvan.reusser@gmail.com> * fix: use get project func * fix: return err not nil * fix: change error to caos err Co-authored-by: adlerhurst <silvan.reusser@gmail.com> Co-authored-by: livio-a <livio.a@gmail.com>
This commit is contained in:
parent
d0e72713fc
commit
bd33b54ac5
@ -31,28 +31,31 @@ func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
|
||||
existingProject, err := repo.ProjectByID(ctx, project.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.ProjectEvents.UpdateProject(ctx, existingProject, project)
|
||||
return repo.ProjectEvents.UpdateProject(ctx, project)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) DeactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
|
||||
project, err := repo.ProjectByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.ProjectEvents.DeactivateProject(ctx, project)
|
||||
return repo.ProjectEvents.DeactivateProject(ctx, id)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ReactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
|
||||
project, err := repo.ProjectByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.ProjectEvents.ReactivateProject(ctx, project)
|
||||
return repo.ProjectEvents.ReactivateProject(ctx, id)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ProjectMemberByID(ctx context.Context, projectID, userID string) (member *proj_model.ProjectMember, err error) {
|
||||
member = proj_model.NewProjectMember(projectID, userID)
|
||||
return repo.ProjectEvents.ProjectMemberByIDs(ctx, member)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) AddProjectMember(ctx context.Context, member *proj_model.ProjectMember) (*proj_model.ProjectMember, error) {
|
||||
return repo.ProjectEvents.AddProjectMember(ctx, member)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) ChangeProjectMember(ctx context.Context, member *proj_model.ProjectMember) (*proj_model.ProjectMember, error) {
|
||||
return repo.ProjectEvents.ChangeProjectMember(ctx, member)
|
||||
}
|
||||
|
||||
func (repo *ProjectRepo) RemoveProjectMember(ctx context.Context, projectID, userID string) error {
|
||||
member := proj_model.NewProjectMember(projectID, userID)
|
||||
return repo.ProjectEvents.RemoveProjectMember(ctx, member)
|
||||
}
|
||||
|
@ -11,4 +11,9 @@ type ProjectRepository interface {
|
||||
UpdateProject(ctx context.Context, project *model.Project) (*model.Project, error)
|
||||
DeactivateProject(ctx context.Context, id string) (*model.Project, error)
|
||||
ReactivateProject(ctx context.Context, id string) (*model.Project, error)
|
||||
|
||||
ProjectMemberByID(ctx context.Context, projectID, userID string) (*model.ProjectMember, error)
|
||||
AddProjectMember(ctx context.Context, member *model.ProjectMember) (*model.ProjectMember, error)
|
||||
ChangeProjectMember(ctx context.Context, member *model.ProjectMember) (*model.ProjectMember, error)
|
||||
RemoveProjectMember(ctx context.Context, projectID, userID string) error
|
||||
}
|
||||
|
@ -8,8 +8,9 @@ import (
|
||||
type Project struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
State ProjectState
|
||||
Name string
|
||||
State ProjectState
|
||||
Name string
|
||||
Members []*ProjectMember
|
||||
}
|
||||
|
||||
type ProjectState in_model.Enum
|
||||
@ -33,3 +34,12 @@ func (p *Project) IsValid() bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Project) ContainsMember(member *ProjectMember) bool {
|
||||
for _, m := range p.Members {
|
||||
if m.UserID == member.UserID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
18
internal/project/model/project_member.go
Normal file
18
internal/project/model/project_member.go
Normal file
@ -0,0 +1,18 @@
|
||||
package model
|
||||
|
||||
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
type ProjectMember struct {
|
||||
es_models.ObjectRoot
|
||||
|
||||
UserID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func NewProjectMember(projectID, userID string) *ProjectMember {
|
||||
return &ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: projectID}, UserID: userID}
|
||||
}
|
||||
|
||||
func (p *ProjectMember) IsValid() bool {
|
||||
return p.ID != "" && p.UserID != "" && len(p.Roles) != 0
|
||||
}
|
@ -2,6 +2,7 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||
@ -51,15 +52,19 @@ func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_mo
|
||||
return ProjectToModel(repoProject), nil
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) UpdateProject(ctx context.Context, existingProject *proj_model.Project, project *proj_model.Project) (*proj_model.Project, error) {
|
||||
func (es *ProjectEventstore) UpdateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
|
||||
if !project.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
|
||||
}
|
||||
existingProject, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: project.ID, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoExisting := ProjectFromModel(existingProject)
|
||||
repoNew := ProjectFromModel(project)
|
||||
|
||||
updateAggregate := ProjectUpdateAggregate(es.AggregateCreator(), repoExisting, repoNew)
|
||||
err := es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -67,7 +72,11 @@ func (es *ProjectEventstore) UpdateProject(ctx context.Context, existingProject
|
||||
return ProjectToModel(repoExisting), nil
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
|
||||
func (es *ProjectEventstore) DeactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
|
||||
existing, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: id, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existing.IsActive() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active")
|
||||
}
|
||||
@ -78,7 +87,11 @@ func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *pr
|
||||
return ProjectToModel(repoExisting), nil
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
|
||||
func (es *ProjectEventstore) ReactivateProject(ctx context.Context, id string) (*proj_model.Project, error) {
|
||||
existing, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: id, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing.IsActive() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive")
|
||||
}
|
||||
@ -88,3 +101,87 @@ func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *pr
|
||||
es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
||||
return ProjectToModel(repoExisting), nil
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) ProjectMemberByIDs(ctx context.Context, member *proj_model.ProjectMember) (*proj_model.ProjectMember, error) {
|
||||
if member.UserID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ld93d", "userID missing")
|
||||
}
|
||||
project, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: member.ID, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range project.Members {
|
||||
if m.UserID == member.UserID {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return nil, caos_errs.ThrowInternal(nil, "EVENT-3udjs", "Could not find member in list")
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) AddProjectMember(ctx context.Context, member *proj_model.ProjectMember) (*proj_model.ProjectMember, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "UserID and Roles are required")
|
||||
}
|
||||
existing, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: member.ID, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing.ContainsMember(member) {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-idke6", "User is already member of this Project")
|
||||
}
|
||||
repoProject := ProjectFromModel(existing)
|
||||
repoMember := ProjectMemberFromModel(member)
|
||||
|
||||
addAggregate := ProjectMemberAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate)
|
||||
for _, m := range repoProject.Members {
|
||||
if m.UserID == member.UserID {
|
||||
return ProjectMemberToModel(m), nil
|
||||
}
|
||||
}
|
||||
return nil, caos_errs.ThrowInternal(nil, "EVENT-3udjs", "Could not find member in list")
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) ChangeProjectMember(ctx context.Context, member *proj_model.ProjectMember) (*proj_model.ProjectMember, error) {
|
||||
if !member.IsValid() {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "UserID and Roles are required")
|
||||
}
|
||||
existing, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: member.ID, Sequence: 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existing.ContainsMember(member) {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-oe39f", "User is not member of this project")
|
||||
}
|
||||
repoProject := ProjectFromModel(existing)
|
||||
repoMember := ProjectMemberFromModel(member)
|
||||
|
||||
projectAggregate := ProjectMemberChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
|
||||
|
||||
for _, m := range repoProject.Members {
|
||||
if m.UserID == member.UserID {
|
||||
return ProjectMemberToModel(m), nil
|
||||
}
|
||||
}
|
||||
return nil, caos_errs.ThrowInternal(nil, "EVENT-3udjs", "Could not find member in list")
|
||||
}
|
||||
|
||||
func (es *ProjectEventstore) RemoveProjectMember(ctx context.Context, member *proj_model.ProjectMember) error {
|
||||
if member.UserID == "" {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-d43fs", "UserID and Roles are required")
|
||||
}
|
||||
existing, err := es.ProjectByID(ctx, &proj_model.Project{ObjectRoot: models.ObjectRoot{ID: member.ID, Sequence: 0}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !existing.ContainsMember(member) {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-swf34", "User is not member of this project")
|
||||
}
|
||||
repoProject := ProjectFromModel(existing)
|
||||
repoMember := ProjectMemberFromModel(member)
|
||||
|
||||
projectAggregate := ProjectMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoMember)
|
||||
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
|
||||
return err
|
||||
}
|
||||
|
@ -26,8 +26,61 @@ func GetMockProjectByIDNoEvents(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
}
|
||||
|
||||
func GetMockManipulateProject(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
data, _ := json.Marshal(Project{Name: "Name"})
|
||||
events := []*es_models.Event{
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded, Data: data},
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||
return &ProjectEventstore{Eventstore: mockEs}
|
||||
}
|
||||
|
||||
func GetMockManipulateInactiveProject(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
data, _ := json.Marshal(Project{Name: "Name"})
|
||||
events := []*es_models.Event{
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded, Data: data},
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 2, Type: model.ProjectDeactivated, Data: data},
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||
return &ProjectEventstore{Eventstore: mockEs}
|
||||
}
|
||||
|
||||
func GetMockManipulateProjectWithMember(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
data, _ := json.Marshal(Project{Name: "Name"})
|
||||
memberData, _ := json.Marshal(ProjectMember{UserID: "UserID", Roles: []string{"Role"}})
|
||||
events := []*es_models.Event{
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded, Data: data},
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectMemberAdded, Data: memberData},
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||
return &ProjectEventstore{Eventstore: mockEs}
|
||||
}
|
||||
|
||||
func GetMockManipulateProjectNoEvents(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
events := []*es_models.Event{}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||
return &ProjectEventstore{Eventstore: mockEs}
|
||||
}
|
||||
|
||||
func GetMockProjectMemberByIDsOK(ctrl *gomock.Controller) *ProjectEventstore {
|
||||
projectData, _ := json.Marshal(Project{Name: "Name"})
|
||||
memberData, _ := json.Marshal(ProjectMember{UserID: "UserID", Roles: []string{"Role"}})
|
||||
events := []*es_models.Event{
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded, Data: projectData},
|
||||
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectMemberAdded, Data: memberData},
|
||||
}
|
||||
mockEs := mock.NewMockEventstore(ctrl)
|
||||
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||
return &ProjectEventstore{Eventstore: mockEs}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ func TestCreateProject(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "project from events, ok",
|
||||
name: "create project, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
@ -134,10 +134,9 @@ func TestCreateProject(t *testing.T) {
|
||||
func TestUpdateProject(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *ProjectEventstore
|
||||
ctx context.Context
|
||||
existing *model.Project
|
||||
new *model.Project
|
||||
es *ProjectEventstore
|
||||
ctx context.Context
|
||||
new *model.Project
|
||||
}
|
||||
type res struct {
|
||||
project *model.Project
|
||||
@ -150,34 +149,44 @@ func TestUpdateProject(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "project from events, ok",
|
||||
name: "update project, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
|
||||
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
|
||||
},
|
||||
res: res{
|
||||
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create project no name",
|
||||
name: "update project no name",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
|
||||
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: ""},
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: ""},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing project not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.UpdateProject(tt.args.ctx, tt.args.existing, tt.args.new)
|
||||
result, err := tt.args.es.UpdateProject(tt.args.ctx, tt.args.new)
|
||||
|
||||
if !tt.res.wantErr && result.ID == "" {
|
||||
t.Errorf("result has no id")
|
||||
@ -224,7 +233,7 @@ func TestDeactivateProject(t *testing.T) {
|
||||
{
|
||||
name: "deactivate project with inactive state",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
es: GetMockManipulateInactiveProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Inactive},
|
||||
},
|
||||
@ -233,10 +242,22 @@ func TestDeactivateProject(t *testing.T) {
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Active},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.DeactivateProject(tt.args.ctx, tt.args.existing)
|
||||
result, err := tt.args.es.DeactivateProject(tt.args.ctx, tt.args.existing.ID)
|
||||
|
||||
if !tt.res.wantErr && result.ID == "" {
|
||||
t.Errorf("result has no id")
|
||||
@ -270,9 +291,9 @@ func TestReactivateProject(t *testing.T) {
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "deactivate project, ok",
|
||||
name: "reactivate project, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
es: GetMockManipulateInactiveProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Inactive},
|
||||
},
|
||||
@ -281,7 +302,7 @@ func TestReactivateProject(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deactivate project with inactive state",
|
||||
name: "reactivate project with inactive state",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
@ -292,10 +313,22 @@ func TestReactivateProject(t *testing.T) {
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Active},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.ReactivateProject(tt.args.ctx, tt.args.existing)
|
||||
result, err := tt.args.es.ReactivateProject(tt.args.ctx, tt.args.existing.ID)
|
||||
|
||||
if !tt.res.wantErr && result.ID == "" {
|
||||
t.Errorf("result has no id")
|
||||
@ -309,3 +342,361 @@ func TestReactivateProject(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectMemberByIDs(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *ProjectEventstore
|
||||
member *model.ProjectMember
|
||||
}
|
||||
type res struct {
|
||||
member *model.ProjectMember
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "project member from events, ok",
|
||||
args: args{
|
||||
es: GetMockProjectMemberByIDsOK(ctrl),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID"},
|
||||
},
|
||||
res: res{
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Role"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project member from events, no events",
|
||||
args: args{
|
||||
es: GetMockProjectByIDNoEvents(ctrl),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID"},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project member from events, no id",
|
||||
args: args{
|
||||
es: GetMockProjectByIDNoEvents(ctrl),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: func(err error) bool {
|
||||
return caos_errs.IsPreconditionFailed(err)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.ProjectMemberByIDs(nil, tt.args.member)
|
||||
if !tt.res.wantErr && result.ID != tt.res.member.ID {
|
||||
t.Errorf("got wrong result id: expected: %v, actual: %v ", tt.res.member.ID, result.ID)
|
||||
}
|
||||
if !tt.res.wantErr && result.UserID != tt.res.member.UserID {
|
||||
t.Errorf("got wrong result userid: expected: %v, actual: %v ", tt.res.member.UserID, result.UserID)
|
||||
}
|
||||
if !tt.res.wantErr && len(result.Roles) != len(tt.res.member.Roles) {
|
||||
t.Errorf("got wrong result roles: expected: %v, actual: %v ", tt.res.member.Roles, result.Roles)
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddProjectMember(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *ProjectEventstore
|
||||
ctx context.Context
|
||||
member *model.ProjectMember
|
||||
}
|
||||
type res struct {
|
||||
result *model.ProjectMember
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add project member, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
result: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no userid",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no roles",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID"},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member already existing",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectWithMember(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing project not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.AddProjectMember(tt.args.ctx, tt.args.member)
|
||||
|
||||
if !tt.res.wantErr && result.ID == "" {
|
||||
t.Errorf("result has no id")
|
||||
}
|
||||
if !tt.res.wantErr && result.UserID != tt.res.result.UserID {
|
||||
t.Errorf("got wrong result userid: expected: %v, actual: %v ", tt.res.result.UserID, result.UserID)
|
||||
}
|
||||
if !tt.res.wantErr && len(result.Roles) != len(tt.res.result.Roles) {
|
||||
t.Errorf("got wrong result roles: expected: %v, actual: %v ", tt.res.result.Roles, result.Roles)
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeProjectMember(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *ProjectEventstore
|
||||
ctx context.Context
|
||||
member *model.ProjectMember
|
||||
}
|
||||
type res struct {
|
||||
result *model.ProjectMember
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "add project member, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectWithMember(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
|
||||
},
|
||||
res: res{
|
||||
result: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no userid",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Roles: []string{"ChangeRoles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no roles",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID"},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := tt.args.es.ChangeProjectMember(tt.args.ctx, tt.args.member)
|
||||
|
||||
if !tt.res.wantErr && result.ID == "" {
|
||||
t.Errorf("result has no id")
|
||||
}
|
||||
if !tt.res.wantErr && result.UserID != tt.res.result.UserID {
|
||||
t.Errorf("got wrong result userid: expected: %v, actual: %v ", tt.res.result.UserID, result.UserID)
|
||||
}
|
||||
if !tt.res.wantErr && len(result.Roles) != len(tt.res.result.Roles) {
|
||||
t.Errorf("got wrong result roles: expected: %v, actual: %v ", tt.res.result.Roles, result.Roles)
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveProjectMember(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
type args struct {
|
||||
es *ProjectEventstore
|
||||
ctx context.Context
|
||||
existing *model.Project
|
||||
member *model.ProjectMember
|
||||
}
|
||||
type res struct {
|
||||
result *model.ProjectMember
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "remove project member, ok",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectWithMember(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{
|
||||
ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1},
|
||||
Name: "Name",
|
||||
Members: []*model.ProjectMember{&model.ProjectMember{UserID: "UserID", Roles: []string{"Roles"}}},
|
||||
},
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID"},
|
||||
},
|
||||
res: res{
|
||||
result: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no userid",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{
|
||||
ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1},
|
||||
Name: "Name",
|
||||
Members: []*model.ProjectMember{&model.ProjectMember{UserID: "UserID", Roles: []string{"Roles"}}},
|
||||
},
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Roles: []string{"ChangeRoles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "member not existing",
|
||||
args: args{
|
||||
es: GetMockManipulateProject(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
existing: &model.Project{
|
||||
ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1},
|
||||
Name: "Name",
|
||||
},
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"Roles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing not found",
|
||||
args: args{
|
||||
es: GetMockManipulateProjectNoEvents(ctrl),
|
||||
ctx: auth.NewMockContext("orgID", "userID"),
|
||||
member: &model.ProjectMember{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, UserID: "UserID", Roles: []string{"ChangeRoles"}},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
errFunc: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.es.RemoveProjectMember(tt.args.ctx, tt.args.member)
|
||||
|
||||
if !tt.res.wantErr && err != nil {
|
||||
t.Errorf("should not get err")
|
||||
}
|
||||
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
|
||||
"github.com/caos/logging"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
@ -14,11 +15,27 @@ const (
|
||||
|
||||
type Project struct {
|
||||
es_models.ObjectRoot
|
||||
Name string `json:"name,omitempty"`
|
||||
State int32 `json:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
State int32 `json:"-"`
|
||||
Members []*ProjectMember `json:"-"`
|
||||
}
|
||||
|
||||
type ProjectMember struct {
|
||||
es_models.ObjectRoot
|
||||
UserID string `json:"userId,omitempty"`
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Project) Changes(changed *Project) map[string]interface{} {
|
||||
changes := make(map[string]interface{}, 1)
|
||||
if changed.Name != "" && p.Name != changed.Name {
|
||||
changes["name"] = changed.Name
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func ProjectFromModel(project *model.Project) *Project {
|
||||
members := ProjectMembersFromModel(project.Members)
|
||||
return &Project{
|
||||
ObjectRoot: es_models.ObjectRoot{
|
||||
ID: project.ObjectRoot.ID,
|
||||
@ -26,12 +43,14 @@ func ProjectFromModel(project *model.Project) *Project {
|
||||
ChangeDate: project.ChangeDate,
|
||||
CreationDate: project.CreationDate,
|
||||
},
|
||||
Name: project.Name,
|
||||
State: model.ProjectStateToInt(project.State),
|
||||
Name: project.Name,
|
||||
State: model.ProjectStateToInt(project.State),
|
||||
Members: members,
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectToModel(project *Project) *model.Project {
|
||||
members := ProjectMembersToModel(project.Members)
|
||||
return &model.Project{
|
||||
ObjectRoot: es_models.ObjectRoot{
|
||||
ID: project.ID,
|
||||
@ -39,8 +58,51 @@ func ProjectToModel(project *Project) *model.Project {
|
||||
CreationDate: project.CreationDate,
|
||||
Sequence: project.Sequence,
|
||||
},
|
||||
Name: project.Name,
|
||||
State: model.ProjectStateFromInt(project.State),
|
||||
Name: project.Name,
|
||||
State: model.ProjectStateFromInt(project.State),
|
||||
Members: members,
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectMembersToModel(members []*ProjectMember) []*model.ProjectMember {
|
||||
convertedMembers := make([]*model.ProjectMember, len(members))
|
||||
for i, m := range members {
|
||||
convertedMembers[i] = ProjectMemberToModel(m)
|
||||
}
|
||||
return convertedMembers
|
||||
}
|
||||
|
||||
func ProjectMembersFromModel(members []*model.ProjectMember) []*ProjectMember {
|
||||
convertedMembers := make([]*ProjectMember, len(members))
|
||||
for i, m := range members {
|
||||
convertedMembers[i] = ProjectMemberFromModel(m)
|
||||
}
|
||||
return convertedMembers
|
||||
}
|
||||
|
||||
func ProjectMemberFromModel(member *model.ProjectMember) *ProjectMember {
|
||||
return &ProjectMember{
|
||||
ObjectRoot: es_models.ObjectRoot{
|
||||
ID: member.ObjectRoot.ID,
|
||||
Sequence: member.Sequence,
|
||||
ChangeDate: member.ChangeDate,
|
||||
CreationDate: member.CreationDate,
|
||||
},
|
||||
UserID: member.UserID,
|
||||
Roles: member.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectMemberToModel(member *ProjectMember) *model.ProjectMember {
|
||||
return &model.ProjectMember{
|
||||
ObjectRoot: es_models.ObjectRoot{
|
||||
ID: member.ID,
|
||||
ChangeDate: member.ChangeDate,
|
||||
CreationDate: member.CreationDate,
|
||||
Sequence: member.Sequence,
|
||||
},
|
||||
UserID: member.UserID,
|
||||
Roles: member.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,14 +114,6 @@ func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project,
|
||||
return project, project.AppendEvents(events...)
|
||||
}
|
||||
|
||||
func (p *Project) Changes(changed *Project) map[string]interface{} {
|
||||
changes := make(map[string]interface{}, 1)
|
||||
if changed.Name != "" && p.Name != changed.Name {
|
||||
changes["name"] = changed.Name
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
func (p *Project) AppendEvents(events ...*es_models.Event) error {
|
||||
for _, event := range events {
|
||||
if err := p.AppendEvent(event); err != nil {
|
||||
@ -84,8 +138,13 @@ func (p *Project) AppendEvent(event *es_models.Event) error {
|
||||
return p.appendDeactivatedEvent()
|
||||
case model.ProjectReactivated:
|
||||
return p.appendReactivatedEvent()
|
||||
case model.ProjectMemberAdded:
|
||||
return p.appendAddMemberEvent(event)
|
||||
case model.ProjectMemberChanged:
|
||||
return p.appendChangeMemberEvent(event)
|
||||
case model.ProjectMemberRemoved:
|
||||
return p.appendRemoveMemberEvent(event)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -98,3 +157,51 @@ func (p *Project) appendReactivatedEvent() error {
|
||||
p.State = model.ProjectStateToInt(model.Active)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) appendAddMemberEvent(event *es_models.Event) error {
|
||||
member, err := getMemberData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
member.ObjectRoot.CreationDate = event.CreationDate
|
||||
p.Members = append(p.Members, member)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) appendChangeMemberEvent(event *es_models.Event) error {
|
||||
member, err := getMemberData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, m := range p.Members {
|
||||
if m.UserID == member.UserID {
|
||||
p.Members[i] = member
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) appendRemoveMemberEvent(event *es_models.Event) error {
|
||||
member, err := getMemberData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, m := range p.Members {
|
||||
if m.UserID == member.UserID {
|
||||
p.Members[i] = p.Members[len(p.Members)-1]
|
||||
p.Members[len(p.Members)-1] = nil
|
||||
p.Members = p.Members[:len(p.Members)-1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMemberData(event *es_models.Event) (*ProjectMember, error) {
|
||||
member := &ProjectMember{}
|
||||
member.ObjectRoot.AppendEvent(event)
|
||||
if err := json.Unmarshal(event.Data, member); err != nil {
|
||||
logging.Log("EVEN-e4dkp").WithError(err).Error("could not unmarshal event data")
|
||||
return nil, errors.ThrowInternal(err, "EVENT-83js6", "could not unmarshal event data")
|
||||
}
|
||||
return member, nil
|
||||
}
|
||||
|
@ -212,3 +212,115 @@ func TestChanges(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestAppendAddMemberEvent(t *testing.T) {
|
||||
type args struct {
|
||||
project *Project
|
||||
member *ProjectMember
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *Project
|
||||
}{
|
||||
{
|
||||
name: "append add member event",
|
||||
args: args{
|
||||
project: &Project{},
|
||||
member: &ProjectMember{UserID: "UserID", Roles: []string{"Role"}},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &Project{Members: []*ProjectMember{&ProjectMember{UserID: "UserID", Roles: []string{"Role"}}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.member != nil {
|
||||
data, _ := json.Marshal(tt.args.member)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.project.appendAddMemberEvent(tt.args.event)
|
||||
if len(tt.args.project.Members) != 1 {
|
||||
t.Errorf("got wrong result should have one member actual: %v ", len(tt.args.project.Members))
|
||||
}
|
||||
if tt.args.project.Members[0] == tt.result.Members[0] {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.Members[0], tt.args.project.Members[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendChangeMemberEvent(t *testing.T) {
|
||||
type args struct {
|
||||
project *Project
|
||||
member *ProjectMember
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *Project
|
||||
}{
|
||||
{
|
||||
name: "append change member event",
|
||||
args: args{
|
||||
project: &Project{Members: []*ProjectMember{&ProjectMember{UserID: "UserID", Roles: []string{"Role"}}}},
|
||||
member: &ProjectMember{UserID: "UserID", Roles: []string{"ChangedRole"}},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &Project{Members: []*ProjectMember{&ProjectMember{UserID: "UserID", Roles: []string{"ChangedRole"}}}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.member != nil {
|
||||
data, _ := json.Marshal(tt.args.member)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.project.appendChangeMemberEvent(tt.args.event)
|
||||
if len(tt.args.project.Members) != 1 {
|
||||
t.Errorf("got wrong result should have one member actual: %v ", len(tt.args.project.Members))
|
||||
}
|
||||
if tt.args.project.Members[0] == tt.result.Members[0] {
|
||||
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.Members[0], tt.args.project.Members[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendRemoveMemberEvent(t *testing.T) {
|
||||
type args struct {
|
||||
project *Project
|
||||
member *ProjectMember
|
||||
event *es_models.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result *Project
|
||||
}{
|
||||
{
|
||||
name: "append remove member event",
|
||||
args: args{
|
||||
project: &Project{Members: []*ProjectMember{&ProjectMember{UserID: "UserID", Roles: []string{"Role"}}}},
|
||||
member: &ProjectMember{UserID: "UserID"},
|
||||
event: &es_models.Event{},
|
||||
},
|
||||
result: &Project{Members: []*ProjectMember{}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.member != nil {
|
||||
data, _ := json.Marshal(tt.args.member)
|
||||
tt.args.event.Data = data
|
||||
}
|
||||
tt.args.project.appendRemoveMemberEvent(tt.args.event)
|
||||
if len(tt.args.project.Members) != 0 {
|
||||
t.Errorf("got wrong result should have one member actual: %v ", len(tt.args.project.Members))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -89,3 +89,43 @@ func projectStateAggregate(aggCreator *es_models.AggregateCreator, project *Proj
|
||||
return agg.AppendEvent(state, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectMemberAddedAggregate(aggCreator *es_models.AggregateCreator, existing *Project, member *ProjectMember) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if existing == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-di38f", "existing project should not be nil")
|
||||
}
|
||||
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.ProjectMemberAdded, member)
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectMemberChangedAggregate(aggCreator *es_models.AggregateCreator, existing *Project, member *ProjectMember) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if existing == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-sle3d", "existing project should not be nil")
|
||||
}
|
||||
|
||||
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.ProjectMemberChanged, member)
|
||||
}
|
||||
}
|
||||
|
||||
func ProjectMemberRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *Project, member *ProjectMember) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||
if existing == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-slo9e", "existing project should not be nil")
|
||||
}
|
||||
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agg.AppendEvent(model.ProjectMemberRemoved, member)
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1220,7 +1220,7 @@
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
"$ref": "#/definitions/v1ProjectMember"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1280,7 +1280,7 @@
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"properties": {}
|
||||
"$ref": "#/definitions/v1ProjectMember"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3213,7 +3213,7 @@
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/protobufStruct"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3224,6 +3224,19 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"protobufListValue": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/protobufValue"
|
||||
},
|
||||
"description": "Repeated field of dynamically typed values."
|
||||
}
|
||||
},
|
||||
"description": "`ListValue` is a wrapper around a repeated field of values.\n\nThe JSON representation for `ListValue` is JSON array."
|
||||
},
|
||||
"protobufNullValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@ -3232,6 +3245,51 @@
|
||||
"default": "NULL_VALUE",
|
||||
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
|
||||
},
|
||||
"protobufStruct": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/protobufValue"
|
||||
},
|
||||
"description": "Unordered map of dynamically typed values."
|
||||
}
|
||||
},
|
||||
"description": "`Struct` represents a structured data value, consisting of fields\nwhich map to dynamically typed values. In some languages, `Struct`\nmight be supported by a native representation. For example, in\nscripting languages like JS a struct is represented as an\nobject. The details of that representation are described together\nwith the proto support for the language.\n\nThe JSON representation for `Struct` is JSON object."
|
||||
},
|
||||
"protobufValue": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"null_value": {
|
||||
"$ref": "#/definitions/protobufNullValue",
|
||||
"description": "Represents a null value."
|
||||
},
|
||||
"number_value": {
|
||||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "Represents a double value."
|
||||
},
|
||||
"string_value": {
|
||||
"type": "string",
|
||||
"description": "Represents a string value."
|
||||
},
|
||||
"bool_value": {
|
||||
"type": "boolean",
|
||||
"format": "boolean",
|
||||
"description": "Represents a boolean value."
|
||||
},
|
||||
"struct_value": {
|
||||
"$ref": "#/definitions/protobufStruct",
|
||||
"description": "Represents a structured value."
|
||||
},
|
||||
"list_value": {
|
||||
"$ref": "#/definitions/protobufListValue",
|
||||
"description": "Represents a repeated `Value`."
|
||||
}
|
||||
},
|
||||
"description": "`Value` represents a dynamically typed value which can be either\nnull, a number, a string, a boolean, a recursive struct value, or a\nlist of values. A producer of value is expected to set one of that\nvariants, absence of any variant indicates an error.\n\nThe JSON representation for `Value` is JSON value."
|
||||
},
|
||||
"v1AddOrgMemberRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -3490,7 +3548,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/protobufStruct"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -78,14 +78,14 @@ func (mr *MockManagementServiceClientMockRecorder) AddProjectGrantMember(arg0, a
|
||||
}
|
||||
|
||||
// AddProjectMember mocks base method
|
||||
func (m *MockManagementServiceClient) AddProjectMember(arg0 context.Context, arg1 *grpc.ProjectMemberAdd, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
||||
func (m *MockManagementServiceClient) AddProjectMember(arg0 context.Context, arg1 *grpc.ProjectMemberAdd, arg2 ...grpc0.CallOption) (*grpc.ProjectMember, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddProjectMember", varargs...)
|
||||
ret0, _ := ret[0].(*emptypb.Empty)
|
||||
ret0, _ := ret[0].(*grpc.ProjectMember)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@ -198,14 +198,14 @@ func (mr *MockManagementServiceClientMockRecorder) ChangeProjectGrantMember(arg0
|
||||
}
|
||||
|
||||
// ChangeProjectMember mocks base method
|
||||
func (m *MockManagementServiceClient) ChangeProjectMember(arg0 context.Context, arg1 *grpc.ProjectMemberChange, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
||||
func (m *MockManagementServiceClient) ChangeProjectMember(arg0 context.Context, arg1 *grpc.ProjectMemberChange, arg2 ...grpc0.CallOption) (*grpc.ProjectMember, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ChangeProjectMember", varargs...)
|
||||
ret0, _ := ret[0].(*emptypb.Empty)
|
||||
ret0, _ := ret[0].(*grpc.ProjectMember)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
@ -14,14 +14,23 @@ func (s *Server) SearchProjectMembers(ctx context.Context, request *ProjectMembe
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-PLr84", "Not implemented")
|
||||
}
|
||||
|
||||
func (s *Server) AddProjectMember(ctx context.Context, in *ProjectMemberAdd) (*empty.Empty, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-c2dks", "Not implemented")
|
||||
func (s *Server) AddProjectMember(ctx context.Context, in *ProjectMemberAdd) (*ProjectMember, error) {
|
||||
member, err := s.project.AddProjectMember(ctx, projectMemberAddToModel(in))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectMemberFromModel(member), nil
|
||||
}
|
||||
|
||||
func (s *Server) ChangeProjectMember(ctx context.Context, in *ProjectMemberChange) (*empty.Empty, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-cms47", "Not implemented")
|
||||
func (s *Server) ChangeProjectMember(ctx context.Context, in *ProjectMemberChange) (*ProjectMember, error) {
|
||||
member, err := s.project.ChangeProjectMember(ctx, projectMemberChangeToModel(in))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectMemberFromModel(member), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveProjectMember(ctx context.Context, in *ProjectMemberRemove) (*empty.Empty, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-olw21", "Not implemented")
|
||||
err := s.project.RemoveProjectMember(ctx, in.Id, in.UserId)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
44
pkg/management/api/grpc/project_member_converter.go
Normal file
44
pkg/management/api/grpc/project_member_converter.go
Normal file
@ -0,0 +1,44 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
)
|
||||
|
||||
func projectMemberFromModel(member *proj_model.ProjectMember) *ProjectMember {
|
||||
creationDate, err := ptypes.TimestampProto(member.CreationDate)
|
||||
logging.Log("GRPC-kd8re").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
changeDate, err := ptypes.TimestampProto(member.ChangeDate)
|
||||
logging.Log("GRPC-dlei3").OnError(err).Debug("unable to parse timestamp")
|
||||
|
||||
return &ProjectMember{
|
||||
CreationDate: creationDate,
|
||||
ChangeDate: changeDate,
|
||||
Sequence: member.Sequence,
|
||||
UserId: member.UserID,
|
||||
Roles: member.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func projectMemberAddToModel(member *ProjectMemberAdd) *proj_model.ProjectMember {
|
||||
return &proj_model.ProjectMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ID: member.Id,
|
||||
},
|
||||
UserID: member.UserId,
|
||||
Roles: member.Roles,
|
||||
}
|
||||
}
|
||||
|
||||
func projectMemberChangeToModel(member *ProjectMemberChange) *proj_model.ProjectMember {
|
||||
return &proj_model.ProjectMember{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ID: member.Id,
|
||||
},
|
||||
UserID: member.UserId,
|
||||
Roles: member.Roles,
|
||||
}
|
||||
}
|
@ -674,7 +674,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc AddProjectMember(ProjectMemberAdd) returns (google.protobuf.Empty) {
|
||||
rpc AddProjectMember(ProjectMemberAdd) returns (ProjectMember) {
|
||||
option (google.api.http) = {
|
||||
post: "/projects/{id}/members"
|
||||
body: "*"
|
||||
@ -686,7 +686,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
rpc ChangeProjectMember(ProjectMemberChange) returns (google.protobuf.Empty) {
|
||||
rpc ChangeProjectMember(ProjectMemberChange) returns (ProjectMember) {
|
||||
option (google.api.http) = {
|
||||
put: "/projects/{id}/members/{user_id}"
|
||||
body: "*"
|
||||
|
Loading…
Reference in New Issue
Block a user