package eventsourcing import ( "context" "encoding/json" "strings" "github.com/caos/logging" "github.com/golang/protobuf/ptypes" "github.com/caos/zitadel/internal/cache/config" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors" es_int "github.com/caos/zitadel/internal/eventstore" es_models "github.com/caos/zitadel/internal/eventstore/models" es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" "github.com/caos/zitadel/internal/id" proj_model "github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" ) const ( projectOwnerRole = "PROJECT_OWNER" projectOwnerGlobalRole = "PROJECT_OWNER_GLOBAL" ) type ProjectEventstore struct { es_int.Eventstore projectCache *ProjectCache passwordAlg crypto.HashAlgorithm pwGenerator crypto.Generator idGenerator id.Generator ClientKeySize int } type ProjectConfig struct { es_int.Eventstore Cache *config.CacheConfig } func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*ProjectEventstore, error) { projectCache, err := StartCache(conf.Cache) if err != nil { return nil, err } passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost) pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg) return &ProjectEventstore{ Eventstore: conf.Eventstore, projectCache: projectCache, passwordAlg: passwordAlg, pwGenerator: pwGenerator, idGenerator: id.SonyFlakeGenerator, ClientKeySize: int(systemDefaults.SecretGenerators.ApplicationKeySize), }, nil } func (es *ProjectEventstore) ProjectByID(ctx context.Context, id string) (*proj_model.Project, error) { project := es.projectCache.getProject(id) query, err := ProjectByIDQuery(project.AggregateID, project.Sequence) if err != nil { return nil, err } err = es_sdk.Filter(ctx, es.FilterEvents, project.AppendEvents, query) if err != nil && !(caos_errs.IsNotFound(err) && project.Sequence != 0) { return nil, err } if project.State == int32(proj_model.ProjectStateRemoved) { return nil, caos_errs.ThrowNotFound(nil, "EVENT-dG8ie", "Errors.Project.NotFound") } es.projectCache.cacheProject(project) return model.ProjectToModel(project), nil } func (es *ProjectEventstore) ProjectEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) { query, err := ProjectByIDQuery(id, sequence) if err != nil { return nil, err } return es.FilterEvents(ctx, query) } func (es *ProjectEventstore) ProjectChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ProjectChanges, error) { query := ChangesQuery(id, lastSequence, limit, sortAscending) events, err := es.Eventstore.FilterEvents(context.Background(), query) if err != nil { logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable") return nil, errors.ThrowInternal(err, "EVENT-328b1", "Errors.Internal") } if len(events) == 0 { return nil, caos_errs.ThrowNotFound(nil, "EVENT-FpQqK", "Errors.Changes.NotFound") } changes := make([]*proj_model.ProjectChange, len(events)) for i, event := range events { creationDate, err := ptypes.TimestampProto(event.CreationDate) logging.Log("EVENT-qxIR7").OnError(err).Debug("unable to parse timestamp") change := &proj_model.ProjectChange{ ChangeDate: creationDate, EventType: event.Type.String(), ModifierId: event.EditorUser, Sequence: event.Sequence, } if event.Data != nil { var data interface{} if strings.Contains(change.EventType, "application") { data = new(model.Application) } else { data = new(model.Project) } err = json.Unmarshal(event.Data, data) logging.Log("EVENT-NCkpN").OnError(err).Debug("unable to unmarshal data") change.Data = data } changes[i] = change if lastSequence < event.Sequence { lastSequence = event.Sequence } } return &proj_model.ProjectChanges{ Changes: changes, LastSequence: lastSequence, }, nil } func ChangesQuery(projectID string, latestSequence, limit uint64, sortAscending bool) *es_models.SearchQuery { query := es_models.NewSearchQuery(). AggregateTypeFilter(model.ProjectAggregate) if !sortAscending { query.OrderDesc() } query.LatestSequenceFilter(latestSequence). AggregateIDFilter(projectID). SetLimit(limit) return query } func (es *ProjectEventstore) ApplicationByIDs(ctx context.Context, projectID, appID string) (*proj_model.Application, error) { if projectID == "" || appID == "" { return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ld93d", "Errors.Project.IDMissing") } project, err := es.ProjectByID(ctx, projectID) if err != nil { return nil, err } if _, a := project.GetApp(appID); a != nil { return a, nil } return nil, caos_errs.ThrowNotFound(nil, "EVENT-8ei2s", "Errors.Project.App.NotFound") } func (es *ProjectEventstore) ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ApplicationChanges, error) { query := ChangesQuery(projectID, lastSequence, limit, sortAscending) events, err := es.Eventstore.FilterEvents(ctx, query) if err != nil { logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable") return nil, errors.ThrowInternal(err, "EVENT-sw6Ku", "Errors.Internal") } if len(events) == 0 { return nil, caos_errs.ThrowNotFound(nil, "EVENT-9IHLP", "Errors.Changes.NotFound") } result := make([]*proj_model.ApplicationChange, 0) for _, event := range events { if !strings.Contains(event.Type.String(), "application") || event.Data == nil { continue } app := new(model.Application) err := json.Unmarshal(event.Data, app) logging.Log("EVENT-GIiKD").OnError(err).Debug("unable to unmarshal data") if app.AppID != appID { continue } creationDate, err := ptypes.TimestampProto(event.CreationDate) logging.Log("EVENT-MJzeN").OnError(err).Debug("unable to parse timestamp") result = append(result, &proj_model.ApplicationChange{ ChangeDate: creationDate, EventType: event.Type.String(), ModifierId: event.EditorUser, Sequence: event.Sequence, Data: app, }) if lastSequence < event.Sequence { lastSequence = event.Sequence } } return &proj_model.ApplicationChanges{ Changes: result, LastSequence: lastSequence, }, nil }