Project commands (#26)

* 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: start implementing project members

* feat: remove member funcs

* feat: remove member model

* feat: remove member events

* feat: remove member repo model

* fix: better error func testing

* Update docs/local.md

Co-Authored-By: Silvan <silvan.reusser@gmail.com>

* Update docs/local.md

Co-Authored-By: Silvan <silvan.reusser@gmail.com>

* fix: mr requests

* fix: md file

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: livio-a <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-04-07 13:23:04 +02:00
committed by GitHub
parent 007fc9e9bd
commit c07ed83c41
61 changed files with 5259 additions and 3481 deletions

View File

@@ -19,11 +19,16 @@ func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID s
return nil, err
}
var perms []string
//TODO: Remove as soon as authentification is implemented
if CheckInternal(ctx) {
return ctx, nil
}
if requiredAuthOption.Permission == authenticated {
return ctx, nil
}
ctx, perms, err := getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
ctx, perms, err = getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
if err != nil {
return nil, err
}
@@ -32,6 +37,7 @@ func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID s
if err != nil {
return nil, err
}
return ctx, nil
}

View File

@@ -2,8 +2,11 @@ package auth
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
"google.golang.org/grpc/metadata"
"strconv"
)
type key int
@@ -38,14 +41,21 @@ type TokenVerifier interface {
}
func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t TokenVerifier) (_ context.Context, err error) {
userID, clientID, agentID, err := verifyAccessToken(ctx, token, t)
if err != nil {
return nil, err
var userID, projectID, clientID, agentID string
//TODO: Remove as soon an authentification is implemented
if CheckInternal(ctx) {
userID = grpc_util.GetHeader(ctx, api.ZitadelUserID)
projectID = grpc_util.GetHeader(ctx, api.ZitadelClientID)
agentID = grpc_util.GetHeader(ctx, api.ZitadelAgentID)
} else {
userID, clientID, agentID, err = verifyAccessToken(ctx, token, t)
if err != nil {
return nil, err
}
projectID, err = t.GetProjectIDByClientID(ctx, clientID)
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
}
projectID, err := t.GetProjectIDByClientID(ctx, clientID)
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
return context.WithValue(ctx, dataKey, CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil
}
@@ -58,3 +68,17 @@ func GetPermissionsFromCtx(ctx context.Context) []string {
ctxPermission, _ := ctx.Value(permissionsKey).([]string)
return ctxPermission
}
//TODO: Remove as soon an authentification is implemented
func CheckInternal(ctx context.Context) bool {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return false
}
v, ok := md[api.LoginKey]
if !ok {
return false
}
ok, _ = strconv.ParseBool(v[0])
return ok
}

View File

@@ -0,0 +1,7 @@
package auth
import "context"
func NewMockContext(orgID, userID string) context.Context {
return context.WithValue(nil, dataKey, CtxData{UserID: userID, OrgID: orgID})
}

View File

@@ -16,7 +16,7 @@ type testVerifier struct {
}
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
return "", "", "", nil
return "userID", "clientID", "agentID", nil
}
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {

View File

@@ -19,11 +19,14 @@ func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Conf
return handler(ctx, req)
}
authToken := grpc_util.GetAuthorizationHeader(ctx)
if authToken == "" {
return nil, status.Error(codes.Unauthenticated, "auth header missing")
authToken := ""
//TODO: Remoce check internal as soon as authentification is implemented
if !auth.CheckInternal(ctx) {
authToken = grpc_util.GetAuthorizationHeader(ctx)
if authToken == "" {
return nil, status.Error(codes.Unauthenticated, "auth header missing")
}
}
orgID := grpc_util.GetHeader(ctx, api.ZitadelOrgID)
ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt)

View File

@@ -9,4 +9,9 @@ const (
Origin = "origin"
ZitadelOrgID = "x-zitadel-orgid"
//TODO: Remove as soon an authentification is implemented
ZitadelUserID = "x-zitadel-userid"
ZitadelClientID = "x-zitadel-clientid"
ZitadelAgentID = "x-zitadel-agentid"
LoginKey = "x-zitadel-login"
)

View File

@@ -57,3 +57,7 @@ func (es *eventstore) FilterEvents(ctx context.Context, searchQuery *models.Sear
}
return es.repo.Filter(ctx, searchQuery)
}
func (es *eventstore) Health(ctx context.Context) error {
return es.repo.Health(ctx)
}

View File

@@ -1,9 +0,0 @@
package eventstore
import (
"context"
)
func (app *eventstore) Health(ctx context.Context) error {
return app.repo.Health(ctx)
}

View File

@@ -24,11 +24,11 @@ func (m *MockRepository) ExpectFilterFail(query *models.SearchQuery, err error)
}
func (m *MockRepository) ExpectPush(aggregates ...*models.Aggregate) *MockRepository {
m.EXPECT().PushEvents(context.Background(), aggregates).Return(nil).MaxTimes(1)
m.EXPECT().PushAggregates(context.Background(), aggregates).Return(nil).MaxTimes(1)
return m
}
func (m *MockRepository) ExpectPushError(err error, aggregates ...*models.Aggregate) *MockRepository {
m.EXPECT().PushEvents(context.Background(), aggregates).Return(err).MaxTimes(1)
m.EXPECT().PushAggregates(context.Background(), aggregates).Return(err).MaxTimes(1)
return m
}

View File

@@ -81,17 +81,3 @@ func (mr *MockRepositoryMockRecorder) PushAggregates(arg0 interface{}, arg1 ...i
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushAggregates", reflect.TypeOf((*MockRepository)(nil).PushAggregates), varargs...)
}
// PushEvents mocks base method
func (m *MockRepository) PushEvents(arg0 context.Context, arg1 [][]*models.Event) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PushEvents", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// PushEvents indicates an expected call of PushEvents
func (mr *MockRepositoryMockRecorder) PushEvents(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushEvents", reflect.TypeOf((*MockRepository)(nil).PushEvents), arg0, arg1)
}

View File

@@ -17,10 +17,11 @@ const (
)
var (
eventColumns = []string{"id", "creation_date", "event_type", "event_sequence", "previous_sequence", "event_data", "editor_service", "editor_user", "resource_owner", "aggregate_type", "aggregate_id", "aggregate_version"}
expectedFilterEventsLimitFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence LIMIT \$1`).String()
expectedFilterEventsDescFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence DESC`).String()
expectedFilterEventsAggregateIDLimit = regexp.MustCompile(selectEscaped + ` WHERE aggregate_id = \$1 ORDER BY event_sequence LIMIT \$2`).String()
expectedFilterEventsAggregateIDTypeLimit = regexp.MustCompile(selectEscaped + ` WHERE aggregate_id = \$1 AND aggregate_type IN \(\$2\) ORDER BY event_sequence LIMIT \$3`).String()
expectedFilterEventsAggregateIDTypeLimit = regexp.MustCompile(selectEscaped + ` WHERE aggregate_id = \$1 AND aggregate_type = ANY\(\$2\) ORDER BY event_sequence LIMIT \$3`).String()
expectedGetAllEvents = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence`).String()
expectedInsertStatement = regexp.MustCompile(`insert into eventstore\.events ` +
@@ -31,8 +32,8 @@ var (
`ELSE NULL ` +
`end ` +
`where \(` +
`\(select count\(id\) from eventstore\.events where event_sequence >= \$14 AND aggregate_type = \$15 AND aggregate_id = \$16\) = 1 OR ` +
`\(\(select count\(id\) from eventstore\.events where aggregate_type = \$17 and aggregate_id = \$18\) = 0 AND \$19 = 0\)\) RETURNING id, event_sequence, creation_date`).String()
`\(select count\(id\) from eventstore\.events where event_sequence >= COALESCE\(\$14, 0\) AND aggregate_type = \$15 AND aggregate_id = \$16\) = 1 OR ` +
`\(\(select count\(id\) from eventstore\.events where aggregate_type = \$17 and aggregate_id = \$18\) = 0 AND COALESCE\(\$19, 0\) = 0\)\) RETURNING id, event_sequence, creation_date`).String()
)
type dbMock struct {
@@ -107,8 +108,8 @@ func (db *dbMock) expectInsertEvent(e *models.Event, returnedID string, returned
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), e.Data, e.EditorUser, e.EditorService, e.ResourceOwner,
e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID,
e.PreviousSequence, e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID, e.PreviousSequence,
Sequence(e.PreviousSequence), e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence),
).
WillReturnRows(
sqlmock.NewRows([]string{"id", "event_sequence", "creation_date"}).
@@ -124,8 +125,8 @@ func (db *dbMock) expectInsertEventError(e *models.Event) *dbMock {
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), e.Data, e.EditorUser, e.EditorService, e.ResourceOwner,
e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID,
e.PreviousSequence, e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID, e.PreviousSequence,
Sequence(e.PreviousSequence), e.AggregateType, e.AggregateID,
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence),
).
WillReturnError(sql.ErrTxDone)
@@ -133,9 +134,9 @@ func (db *dbMock) expectInsertEventError(e *models.Event) *dbMock {
}
func (db *dbMock) expectFilterEventsLimit(limit uint64, eventCount int) *dbMock {
rows := sqlmock.NewRows([]string{"id", "creation_date"})
rows := sqlmock.NewRows(eventColumns)
for i := 0; i < eventCount; i++ {
rows.AddRow(fmt.Sprint("event", i), time.Now())
rows.AddRow(fmt.Sprint("event", i), time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
}
db.mock.ExpectQuery(expectedFilterEventsLimitFormat).
WithArgs(limit).
@@ -144,9 +145,9 @@ func (db *dbMock) expectFilterEventsLimit(limit uint64, eventCount int) *dbMock
}
func (db *dbMock) expectFilterEventsDesc(eventCount int) *dbMock {
rows := sqlmock.NewRows([]string{"id", "creation_date"})
rows := sqlmock.NewRows(eventColumns)
for i := eventCount; i > 0; i-- {
rows.AddRow(fmt.Sprint("event", i), time.Now())
rows.AddRow(fmt.Sprint("event", i), time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
}
db.mock.ExpectQuery(expectedFilterEventsDescFormat).
WillReturnRows(rows)
@@ -154,9 +155,9 @@ func (db *dbMock) expectFilterEventsDesc(eventCount int) *dbMock {
}
func (db *dbMock) expectFilterEventsAggregateIDLimit(aggregateID string, limit uint64) *dbMock {
rows := sqlmock.NewRows([]string{"id", "creation_date"})
rows := sqlmock.NewRows(eventColumns)
for i := limit; i > 0; i-- {
rows.AddRow(fmt.Sprint("event", i), time.Now())
rows.AddRow(fmt.Sprint("event", i), time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
}
db.mock.ExpectQuery(expectedFilterEventsAggregateIDLimit).
WithArgs(aggregateID, limit).
@@ -165,9 +166,9 @@ func (db *dbMock) expectFilterEventsAggregateIDLimit(aggregateID string, limit u
}
func (db *dbMock) expectFilterEventsAggregateIDTypeLimit(aggregateID, aggregateType string, limit uint64) *dbMock {
rows := sqlmock.NewRows([]string{"id", "creation_date"})
rows := sqlmock.NewRows(eventColumns)
for i := limit; i > 0; i-- {
rows.AddRow(fmt.Sprint("event", i), time.Now())
rows.AddRow(fmt.Sprint("event", i), time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
}
db.mock.ExpectQuery(expectedFilterEventsAggregateIDTypeLimit).
WithArgs(aggregateID, pq.Array([]string{aggregateType}), limit).

View File

@@ -57,14 +57,14 @@ func (db *SQL) Filter(ctx context.Context, searchQuery *es_models.SearchQuery) (
for rows.Next() {
event := new(models.Event)
events = append(events, event)
var previousSequence Sequence
rows.Scan(
err = rows.Scan(
&event.ID,
&event.CreationDate,
&event.Type,
&event.Sequence,
&event.PreviousSequence,
&previousSequence,
&event.Data,
&event.EditorService,
&event.EditorUser,
@@ -73,6 +73,14 @@ func (db *SQL) Filter(ctx context.Context, searchQuery *es_models.SearchQuery) (
&event.AggregateID,
&event.AggregateVersion,
)
if err != nil {
logging.Log("SQL-wHNPo").WithError(err).Warn("unable to scan row")
return nil, errors.ThrowInternal(err, "SQL-BfZwF", "unable to scan row")
}
event.PreviousSequence = uint64(previousSequence)
events = append(events, event)
}
return events, nil
@@ -98,7 +106,7 @@ func prepareWhere(searchQuery *es_models.SearchQuery) (clause string, values []i
for i, filter := range searchQuery.Filters {
value := filter.GetValue()
switch value.(type) {
case []bool, []float64, []int64, []string, *[]bool, *[]float64, *[]int64, *[]string:
case []bool, []float64, []int64, []string, []models.AggregateType, *[]bool, *[]float64, *[]int64, *[]string, *[]models.AggregateType:
value = pq.Array(value)
}
@@ -118,7 +126,7 @@ func getCondition(filter *es_models.Filter) string {
func prepareConditionFormat(operation es_models.Operation) string {
if operation == es_models.Operation_In {
return "%s %s (?)"
return "%s %s ANY(?)"
}
return "%s %s ?"
}
@@ -133,9 +141,9 @@ func getField(field es_models.Field) string {
return "event_sequence"
case es_models.Field_ResourceOwner:
return "resource_owner"
case es_models.Field_ModifierService:
case es_models.Field_EditorService:
return "editor_service"
case es_models.Field_ModifierUser:
case es_models.Field_EditorUser:
return "editor_user"
}
return ""
@@ -143,14 +151,12 @@ func getField(field es_models.Field) string {
func getOperation(operation es_models.Operation) string {
switch operation {
case es_models.Operation_Equals:
case es_models.Operation_Equals, es_models.Operation_In:
return "="
case es_models.Operation_Greater:
return ">"
case es_models.Operation_Less:
return "<"
case es_models.Operation_In:
return "IN"
}
return ""
}

View File

@@ -105,7 +105,7 @@ func TestSQL_Filter(t *testing.T) {
}
events, err := sql.Filter(context.Background(), tt.args.searchQuery)
if (err != nil) != tt.wantErr {
t.Errorf("SQL.UnlockAggregates() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("SQL.Filter() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.eventsLen != 0 && len(events) != tt.eventsLen {
t.Errorf("events has wrong length got: %d want %d", len(events), tt.eventsLen)
@@ -142,7 +142,7 @@ func Test_getCondition(t *testing.T) {
args: args{
filter: es_models.NewFilter(es_models.Field_AggregateType, []string{"a", "b"}, es_models.Operation_In),
},
want: "aggregate_type IN (?)",
want: "aggregate_type = ANY(?)",
},
}
for _, tt := range tests {

View File

@@ -20,9 +20,9 @@ const insertStmt = "insert into eventstore.events " +
"end " +
"where (" +
// exactly one event of requested aggregate must have a >= sequence (last inserted event)
"(select count(id) from eventstore.events where event_sequence >= $14 AND aggregate_type = $15 AND aggregate_id = $16) = 1 OR " +
"(select count(id) from eventstore.events where event_sequence >= COALESCE($14, 0) AND aggregate_type = $15 AND aggregate_id = $16) = 1 OR " +
// previous sequence = 0, no events must exist for the requested aggregate
"((select count(id) from eventstore.events where aggregate_type = $17 and aggregate_id = $18) = 0 AND $19 = 0)) " +
"((select count(id) from eventstore.events where aggregate_type = $17 and aggregate_id = $18) = 0 AND COALESCE($19, 0) = 0)) " +
"RETURNING id, event_sequence, creation_date"
func (db *SQL) PushAggregates(ctx context.Context, aggregates ...*models.Aggregate) (err error) {
@@ -52,10 +52,8 @@ func (db *SQL) PushAggregates(ctx context.Context, aggregates ...*models.Aggrega
}
func insertEvents(stmt *sql.Stmt, events []*models.Event) error {
previousSequence := events[0].PreviousSequence
currentSequence := Sequence(events[0].PreviousSequence)
for _, event := range events {
event.PreviousSequence = previousSequence
if event.Data == nil || len(event.Data) == 0 {
//json decoder failes with EOF if json text is empty
event.Data = []byte("{}")
@@ -64,8 +62,8 @@ func insertEvents(stmt *sql.Stmt, events []*models.Event) error {
rows, err := stmt.Query(event.Type, event.AggregateType, event.AggregateID, event.AggregateVersion, event.CreationDate, event.Data, event.EditorUser, event.EditorService, event.ResourceOwner,
event.AggregateType, event.AggregateID,
event.AggregateType, event.AggregateID,
event.PreviousSequence, event.AggregateType, event.AggregateID,
event.AggregateType, event.AggregateID, event.PreviousSequence)
currentSequence, event.AggregateType, event.AggregateID,
event.AggregateType, event.AggregateID, currentSequence)
if err != nil {
logging.Log("SQL-EXA0q").WithError(err).Info("query failed")
@@ -76,7 +74,7 @@ func insertEvents(stmt *sql.Stmt, events []*models.Event) error {
rowInserted := false
for rows.Next() {
rowInserted = true
err = rows.Scan(&event.ID, &event.Sequence, &event.CreationDate)
err = rows.Scan(&event.ID, &currentSequence, &event.CreationDate)
logging.Log("SQL-rAvLD").OnError(err).Info("unable to scan result into event")
}
@@ -84,7 +82,7 @@ func insertEvents(stmt *sql.Stmt, events []*models.Event) error {
return errors.ThrowAlreadyExists(nil, "SQL-GKcAa", "wrong sequence")
}
previousSequence = event.Sequence
event.Sequence = uint64(currentSequence)
}
return nil

View File

@@ -0,0 +1,27 @@
package sql
import (
"database/sql/driver"
)
// Sequence represents a number that may be null.
// Sequence implements the sql.Scanner interface so
type Sequence uint64
// Scan implements the Scanner interface.
func (n *Sequence) Scan(value interface{}) error {
if value == nil {
*n = 0
return nil
}
*n = Sequence(value.(int64))
return nil
}
// Value implements the driver Valuer interface.
func (seq Sequence) Value() (driver.Value, error) {
if seq == 0 {
return nil, nil
}
return int64(seq), nil
}

View File

@@ -7,6 +7,6 @@ const (
Field_AggregateID
Field_LatestSequence
Field_ResourceOwner
Field_ModifierService
Field_ModifierUser
Field_EditorService
Field_EditorUser
)

View File

@@ -33,7 +33,7 @@ func (q *SearchQuery) AggregateIDFilter(id string) *SearchQuery {
return q.setFilter(NewFilter(Field_AggregateID, id, Operation_Equals))
}
func (q *SearchQuery) AggregateTypeFilter(types ...string) *SearchQuery {
func (q *SearchQuery) AggregateTypeFilter(types ...AggregateType) *SearchQuery {
return q.setFilter(NewFilter(Field_AggregateType, types, Operation_In))
}

View File

@@ -0,0 +1,25 @@
package auth
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
)
type TokenVerifier struct {
}
func Start() (v *TokenVerifier) {
return new(TokenVerifier)
}
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
return "", "", "", nil
}
func (v *TokenVerifier) ResolveGrants(ctx context.Context, userID, orgID string) ([]*auth.Grant, error) {
return nil, nil
}
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
}

View File

@@ -1,3 +0,0 @@
package management
type Config struct{}

View File

@@ -0,0 +1,73 @@
package eventsourcing
import (
"context"
proj_model "github.com/caos/zitadel/internal/project/model"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
)
type ProjectRepo struct {
ProjectEvents *proj_event.ProjectEventstore
//view *view.View
}
func (repo *ProjectRepo) ProjectByID(ctx context.Context, id string) (project *proj_model.Project, err error) {
//viewProject, err := repo.view.ProjectByID(id)
//if err != nil && !caos_errs.IsNotFound(err) {
// return nil, err
//}
//if viewProject != nil {
// project = org_view.ProjectToModel(viewProject)
//} else {
project = proj_model.NewProject(id)
//}
return repo.ProjectEvents.ProjectByID(ctx, project)
}
func (repo *ProjectRepo) CreateProject(ctx context.Context, name string) (*proj_model.Project, error) {
project := &proj_model.Project{Name: name}
project, err := repo.ProjectEvents.CreateProject(ctx, project)
if err != nil {
return nil, err
}
return project, nil
}
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
}
project, err = repo.ProjectEvents.UpdateProject(ctx, existingProject, project)
if err != nil {
return nil, err
}
return project, err
}
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
}
project, err = repo.ProjectEvents.DeactivateProject(ctx, project)
if err != nil {
return nil, err
}
return project, err
}
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
}
project, err = repo.ProjectEvents.ReactivateProject(ctx, project)
if err != nil {
return nil, err
}
return project, err
}

View File

@@ -0,0 +1,48 @@
package eventsourcing
import (
"context"
es_int "github.com/caos/zitadel/internal/eventstore"
es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
)
type Config struct {
Eventstore es_int.Config
//View view.ViewConfig
//Spooler spooler.SpoolerConfig
}
type EsRepository struct {
//spooler *es_spooler.Spooler
ProjectRepo
}
func Start(conf Config) (*EsRepository, error) {
es, err := es_int.Start(conf.Eventstore)
if err != nil {
return nil, err
}
//view, sql, err := mgmt_view.StartView(conf.View)
//if err != nil {
// return nil, err
//}
//conf.Spooler.View = view
//conf.Spooler.EsClient = es.Client
//conf.Spooler.SQL = sql
//spool := spooler.StartSpooler(conf.Spooler)
project, err := es_proj.StartProject(es_proj.ProjectConfig{Eventstore: es})
if err != nil {
return nil, err
}
return &EsRepository{
ProjectRepo{project},
}, nil
}
func (repo *EsRepository) Health() error {
return repo.ProjectEvents.Health(context.Background())
}

View File

@@ -0,0 +1,14 @@
package repository
import (
"context"
"github.com/caos/zitadel/internal/project/model"
)
type ProjectRepository interface {
ProjectByID(ctx context.Context, id string) (*model.Project, error)
CreateProject(ctx context.Context, name string) (*model.Project, error)
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)
}

View File

@@ -0,0 +1,6 @@
package repository
type Repository interface {
Health() error
ProjectRepository
}

View File

@@ -0,0 +1,35 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
in_model "github.com/caos/zitadel/internal/model"
)
type Project struct {
es_models.ObjectRoot
State ProjectState
Name string
}
type ProjectState in_model.Enum
var states = []string{"Active", "Inactive"}
func NewProject(id string) *Project {
return &Project{ObjectRoot: es_models.ObjectRoot{ID: id}, State: Active}
}
func (p *Project) IsActive() bool {
if p.State == Active {
return true
}
return false
}
func (p *Project) IsValid() bool {
if p.Name == "" {
return false
}
return true
}

View File

@@ -0,0 +1,23 @@
package model
type state int32
func (s state) String() string {
return states[s]
}
const (
Active state = iota
Inactive
)
func ProjectStateToInt(s ProjectState) int32 {
if s == nil {
return 0
}
return int32(s.(state))
}
func ProjectStateFromInt(index int32) ProjectState {
return state(index)
}

View File

@@ -0,0 +1,37 @@
package model
import "github.com/caos/zitadel/internal/eventstore/models"
const (
ProjectAggregate models.AggregateType = "project"
ProjectAdded models.EventType = "project.added"
ProjectChanged models.EventType = "project.changed"
ProjectDeactivated models.EventType = "project.deactivated"
ProjectReactivated models.EventType = "project.reactivated"
ProjectMemberAdded models.EventType = "project.member.added"
ProjectMemberChanged models.EventType = "project.member.changed"
ProjectMemberRemoved models.EventType = "project.member.removed"
ProjectRoleAdded models.EventType = "project.role.added"
ProjectRoleRemoved models.EventType = "project.role.removed"
ProjectGrantAdded models.EventType = "project.grant.added"
ProjectGrantChanged models.EventType = "project.grant.changed"
ProjectGrantDeactivated models.EventType = "project.grant.deactivated"
ProjectGrantReactivated models.EventType = "project.grant.reactivated"
GrantMemberAdded models.EventType = "project.grant.member.added"
GrantMemberChanged models.EventType = "project.grant.member.changed"
GrantMemberRemoved models.EventType = "project.grant.member.removed"
ApplicationAdded models.EventType = "project.application.added"
ApplicationChanged models.EventType = "project.application.changed"
ApplicationDeactivated models.EventType = "project.application.deactivated"
ApplicationReactivated models.EventType = "project.application.reactivated"
OIDCConfigAdded models.EventType = "project.application.config.oidc.added"
OIDCConfigChanged models.EventType = "project.application.config.oidc.changed"
OIDCConfigSecretChanged models.EventType = "project.application.config.oidc.secret.changed"
)

View File

@@ -0,0 +1,55 @@
package eventsourcing
import (
"encoding/json"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
)
func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, error) {
if project == nil {
project = &Project{}
}
return project, project.AppendEvents(events...)
}
func (p *Project) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := p.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (p *Project) AppendEvent(event *es_models.Event) error {
p.ObjectRoot.AppendEvent(event)
switch event.Type {
case model.ProjectAdded, model.ProjectChanged:
if err := json.Unmarshal(event.Data, p); err != nil {
logging.Log("EVEN-idl93").WithError(err).Error("could not unmarshal event data")
return err
}
p.State = model.ProjectStateToInt(model.Active)
return nil
case model.ProjectDeactivated:
return p.appendDeactivatedEvent()
case model.ProjectReactivated:
return p.appendReactivatedEvent()
}
return nil
}
func (p *Project) appendDeactivatedEvent() error {
p.State = model.ProjectStateToInt(model.Inactive)
return nil
}
func (p *Project) appendReactivatedEvent() error {
p.State = model.ProjectStateToInt(model.Active)
return nil
}

View File

@@ -0,0 +1,169 @@
package eventsourcing
import (
"encoding/json"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"testing"
)
func TestProjectFromEvents(t *testing.T) {
type args struct {
event []*es_models.Event
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "project from events, ok",
args: args{
event: []*es_models.Event{
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "project from events, nil project",
args: args{
event: []*es_models.Event{
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
},
project: nil,
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.project != nil {
data, _ := json.Marshal(tt.args.project)
tt.args.event[0].Data = data
}
result, _ := ProjectFromEvents(tt.args.project, tt.args.event...)
if result.Name != tt.result.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.result.Name, result.Name)
}
})
}
}
func TestAppendEvent(t *testing.T) {
type args struct {
event *es_models.Event
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append added event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "append change event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectChanged},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "append deactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectDeactivated},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Inactive)},
},
{
name: "append reactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectReactivated},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.project != nil {
data, _ := json.Marshal(tt.args.project)
tt.args.event.Data = data
}
result := &Project{}
result.AppendEvent(tt.args.event)
if result.State != tt.result.State {
t.Errorf("got wrong result state: expected: %v, actual: %v ", tt.result.State, result.State)
}
if result.Name != tt.result.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.result.Name, result.Name)
}
if result.ObjectRoot.ID != tt.result.ObjectRoot.ID {
t.Errorf("got wrong result id: expected: %v, actual: %v ", tt.result.ObjectRoot.ID, result.ObjectRoot.ID)
}
})
}
}
func TestAppendDeactivatedEvent(t *testing.T) {
type args struct {
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append reactivate event",
args: args{
project: &Project{},
},
result: &Project{State: int32(model.Inactive)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.project.appendDeactivatedEvent()
if tt.args.project.State != tt.result.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.project)
}
})
}
}
func TestAppendReactivatedEvent(t *testing.T) {
type args struct {
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append reactivate event",
args: args{
project: &Project{},
},
result: &Project{State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.project.appendReactivatedEvent()
if tt.args.project.State != tt.result.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.project)
}
})
}
}

View File

@@ -0,0 +1,110 @@
package eventsourcing
import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore"
proj_model "github.com/caos/zitadel/internal/project/model"
)
type ProjectEventstore struct {
es_int.Eventstore
}
type ProjectConfig struct {
es_int.Eventstore
}
func StartProject(conf ProjectConfig) (*ProjectEventstore, error) {
return &ProjectEventstore{Eventstore: conf.Eventstore}, nil
}
func (es *ProjectEventstore) ProjectByID(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
filter, err := ProjectByIDQuery(project.ID, project.Sequence)
if err != nil {
return nil, err
}
events, err := es.Eventstore.FilterEvents(ctx, filter)
if err != nil {
return nil, err
}
if len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-8due3", "Could not find project events")
}
foundProject, err := ProjectFromEvents(nil, events...)
if err != nil {
return nil, err
}
return ProjectToModel(foundProject), nil
}
func (es *ProjectEventstore) CreateProject(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")
}
project.State = proj_model.Active
repoProject := ProjectFromModel(project)
projectAggregate, err := ProjectCreateAggregate(ctx, es.Eventstore.AggregateCreator(), repoProject)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoProject.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoProject), nil
}
func (es *ProjectEventstore) UpdateProject(ctx context.Context, existing *proj_model.Project, new *proj_model.Project) (*proj_model.Project, error) {
if !new.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
}
repoExisting := ProjectFromModel(existing)
repoNew := ProjectFromModel(new)
projectAggregate, err := ProjectUpdateAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}
func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
if !existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active")
}
repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectDeactivateAggregate(ctx, es.AggregateCreator(), repoExisting)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}
func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
if existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive")
}
repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectReactivateAggregate(ctx, es.AggregateCreator(), repoExisting)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}

View File

@@ -0,0 +1,33 @@
package eventsourcing
import (
"encoding/json"
"github.com/caos/zitadel/internal/eventstore/mock"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/golang/mock/gomock"
)
func GetMockProjectByIDOK(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)
return &ProjectEventstore{Eventstore: mockEs}
}
func GetMockProjectByIDNoEvents(ctrl *gomock.Controller) *ProjectEventstore {
events := []*es_models.Event{}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
return &ProjectEventstore{Eventstore: mockEs}
}
func GetMockManipulateProject(ctrl *gomock.Controller) *ProjectEventstore {
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return &ProjectEventstore{Eventstore: mockEs}
}

View File

@@ -0,0 +1,311 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/golang/mock/gomock"
"testing"
)
func TestProjectByID(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
project *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, ok",
args: args{
es: GetMockProjectByIDOK(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
},
{
name: "project from events, no events",
args: args{
es: GetMockProjectByIDNoEvents(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
{
name: "project from events, no id",
args: args{
es: GetMockProjectByIDNoEvents(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ProjectByID(nil, tt.args.project)
if !tt.res.wantErr && result.ID != tt.res.project.ID {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.ID, result.ID)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCreateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
project *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, ok",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
},
},
{
name: "create project no name",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.CreateProject(tt.args.ctx, tt.args.project)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.Name != tt.res.project.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestUpdateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, 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"},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
},
},
{
name: "create 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: ""},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
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)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.Name != tt.res.project.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestDeactivateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "deactivate 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", State: model.Active},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew", State: model.Inactive},
},
},
{
name: "deactivate project with inactive state",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Inactive},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.DeactivateProject(tt.args.ctx, tt.args.existing)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.State != tt.res.project.State {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.State, result.State)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestReactivateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "deactivate 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", State: model.Inactive},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew", State: model.Active},
},
},
{
name: "deactivate project with inactive state",
args: args{
es: GetMockManipulateProject(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.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ReactivateProject(tt.args.ctx, tt.args.existing)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.State != tt.res.project.State {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.State, result.State)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,131 @@
package eventsourcing
import (
"context"
"strconv"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/sony/sonyflake"
)
var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{})
const (
projectVersion = "v1"
)
type Project struct {
es_models.ObjectRoot
Name string `json:"name,omitempty"`
State int32 `json:"-"`
}
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 {
return &Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ObjectRoot.ID,
Sequence: project.Sequence,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
},
Name: project.Name,
State: model.ProjectStateToInt(project.State),
}
}
func ProjectToModel(project *Project) *model.Project {
return &model.Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ID,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
Sequence: project.Sequence,
},
Name: project.Name,
State: model.ProjectStateFromInt(project.State),
}
}
func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
if id == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled")
}
return ProjectQuery(latestSequence).
AggregateIDFilter(id), nil
}
func ProjectQuery(latestSequence uint64) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(model.ProjectAggregate).
LatestSequenceFilter(latestSequence)
}
func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, id string, sequence uint64) (*es_models.Aggregate, error) {
return aggCreator.NewAggregate(ctx, id, model.ProjectAggregate, projectVersion, sequence)
}
func ProjectCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, project *Project) (*es_models.Aggregate, error) {
if project == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil")
}
var err error
id, err := idGenerator.NextID()
if err != nil {
return nil, err
}
project.ID = strconv.FormatUint(id, 10)
agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.ProjectAdded, project)
}
func ProjectUpdateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project, new *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil")
}
if new == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.Changes(new)
return agg.AppendEvent(model.ProjectChanged, changes)
}
func ProjectDeactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ueh45", "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.ProjectDeactivated, nil)
}
func ProjectReactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "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.ProjectReactivated, nil)
}

View File

@@ -0,0 +1,453 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"testing"
)
func TestChanges(t *testing.T) {
type args struct {
existing *Project
new *Project
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project name changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "NameChanged"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "Name"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestProjectByIDQuery(t *testing.T) {
type args struct {
id string
sequence uint64
}
type res struct {
filterLen int
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project by id query ok",
args: args{
id: "ID",
sequence: 1,
},
res: res{
filterLen: 3,
},
},
{
name: "project by id query, no id",
args: args{
sequence: 1,
},
res: res{
filterLen: 3,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query, err := ProjectByIDQuery(tt.args.id, tt.args.sequence)
if !tt.res.wantErr && query == nil {
t.Errorf("query should not be nil")
}
if !tt.res.wantErr && len(query.Filters) != tt.res.filterLen {
t.Errorf("got wrong filter len: expected: %v, actual: %v ", tt.res.filterLen, len(query.Filters))
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectQuery(t *testing.T) {
type args struct {
sequence uint64
}
type res struct {
filterLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project query ok",
args: args{
sequence: 1,
},
res: res{
filterLen: 2,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query := ProjectQuery(tt.args.sequence)
if query == nil {
t.Errorf("query should not be nil")
}
if len(query.Filters) != tt.res.filterLen {
t.Errorf("got wrong filter len: expected: %v, actual: %v ", tt.res.filterLen, len(query.Filters))
}
})
}
}
func TestProjectAggregate(t *testing.T) {
type args struct {
ctx context.Context
aggCreator *models.AggregateCreator
id string
sequence uint64
}
type res struct {
eventLen int
aggType models.AggregateType
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: models.NewAggregateCreator("Test"),
id: "ID",
sequence: 1,
},
res: res{
eventLen: 0,
aggType: model.ProjectAggregate,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, _ := ProjectAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.id, tt.args.sequence)
if agg == nil {
t.Errorf("agg should not be nil")
}
if len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
})
}
}
func TestProjectCreateAggregate(t *testing.T) {
type args struct {
ctx context.Context
new *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
new: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectAdded,
},
},
{
name: "new project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectAdded,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectCreateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.new)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectUpdateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
new *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
new: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName_Changed", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "new project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectUpdateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectDeactivateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project deactivate aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectDeactivated,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectDeactivated,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectDeactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectReactivateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project reactivate aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Inactive)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectReactivated,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectReactivated,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectReactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -83,7 +83,7 @@ func templatesAuth_method_mappingGoTmpl() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "templates/auth_method_mapping.go.tmpl", size: 1013, mode: os.FileMode(420), modTime: time.Unix(1585129064, 0)}
info := bindataFileInfo{name: "templates/auth_method_mapping.go.tmpl", size: 1013, mode: os.FileMode(420), modTime: time.Unix(1586159062, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -182,7 +182,6 @@ type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": &bintree{nil, map[string]*bintree{
"auth_method_mapping.go.tmpl": &bintree{templatesAuth_method_mappingGoTmpl, map[string]*bintree{}},
@@ -235,3 +234,4 @@ func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}