mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 23:57:31 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
7
internal/api/auth/context_mock.go
Normal file
7
internal/api/auth/context_mock.go
Normal 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})
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
|
@@ -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"
|
||||
)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -1,9 +0,0 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func (app *eventstore) Health(ctx context.Context) error {
|
||||
return app.repo.Health(ctx)
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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).
|
||||
|
@@ -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 ""
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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, ¤tSequence, &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
|
||||
|
27
internal/eventstore/internal/repository/sql/sequence.go
Normal file
27
internal/eventstore/internal/repository/sql/sequence.go
Normal 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
|
||||
}
|
@@ -7,6 +7,6 @@ const (
|
||||
Field_AggregateID
|
||||
Field_LatestSequence
|
||||
Field_ResourceOwner
|
||||
Field_ModifierService
|
||||
Field_ModifierUser
|
||||
Field_EditorService
|
||||
Field_EditorUser
|
||||
)
|
||||
|
@@ -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))
|
||||
}
|
||||
|
||||
|
25
internal/management/auth/token_verifier.go
Normal file
25
internal/management/auth/token_verifier.go
Normal 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
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
package management
|
||||
|
||||
type Config struct{}
|
73
internal/management/repository/eventsourcing/project.go
Normal file
73
internal/management/repository/eventsourcing/project.go
Normal 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
|
||||
}
|
48
internal/management/repository/eventsourcing/repository.go
Normal file
48
internal/management/repository/eventsourcing/repository.go
Normal 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())
|
||||
}
|
14
internal/management/repository/project.go
Normal file
14
internal/management/repository/project.go
Normal 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)
|
||||
}
|
6
internal/management/repository/repository.go
Normal file
6
internal/management/repository/repository.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package repository
|
||||
|
||||
type Repository interface {
|
||||
Health() error
|
||||
ProjectRepository
|
||||
}
|
35
internal/project/model/project.go
Normal file
35
internal/project/model/project.go
Normal 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
|
||||
}
|
23
internal/project/model/state.go
Normal file
23
internal/project/model/state.go
Normal 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)
|
||||
}
|
37
internal/project/model/types.go
Normal file
37
internal/project/model/types.go
Normal 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"
|
||||
)
|
55
internal/project/repository/eventsourcing/events.go
Normal file
55
internal/project/repository/eventsourcing/events.go
Normal 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
|
||||
}
|
169
internal/project/repository/eventsourcing/events_test.go
Normal file
169
internal/project/repository/eventsourcing/events_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
110
internal/project/repository/eventsourcing/eventstore.go
Normal file
110
internal/project/repository/eventsourcing/eventstore.go
Normal 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
|
||||
}
|
33
internal/project/repository/eventsourcing/eventstore_mock.go
Normal file
33
internal/project/repository/eventsourcing/eventstore_mock.go
Normal 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}
|
||||
}
|
311
internal/project/repository/eventsourcing/eventstore_test.go
Normal file
311
internal/project/repository/eventsourcing/eventstore_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
131
internal/project/repository/eventsourcing/project.go
Normal file
131
internal/project/repository/eventsourcing/project.go
Normal 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)
|
||||
}
|
453
internal/project/repository/eventsourcing/project_test.go
Normal file
453
internal/project/repository/eventsourcing/project_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -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, "/")...)...)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user