feat: User metadata (#2025)

* feat: user meta data events

* feat: user meta data set tests

* feat: user meta data tests

* feat: user meta data in protos

* feat: user meta data command api

* feat: user meta data query side

* feat: proto correct order, fix handlers

* feat: proto correct order

* feat: fixes of pr comments

* feat: fixes of pr comments

* feat: value as byte array

* feat: metadata feature

* Update internal/auth/repository/eventsourcing/handler/meta_data.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update internal/command/user_meta_data.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update proto/zitadel/metadata.proto

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update proto/zitadel/metadata.proto

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* fix: rename metadata files and table

* fix: rename meta data to metadat in protos

* Update internal/domain/metadata.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* fix: rename vars

* fix: rebiuld docs

* Update internal/iam/repository/view/metadata_view.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Fabi
2021-08-09 08:01:20 +02:00
committed by GitHub
parent ae50f57c2c
commit 7451ed58f2
40 changed files with 3725 additions and 4 deletions

View File

@@ -0,0 +1,29 @@
package auth
import (
"github.com/caos/zitadel/internal/api/grpc/metadata"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/pkg/grpc/auth"
)
func BulkSetMetadataToDomain(req *auth.BulkSetMyMetadataRequest) []*domain.Metadata {
metadata := make([]*domain.Metadata, len(req.Metadata))
for i, data := range req.Metadata {
metadata[i] = &domain.Metadata{
Key: data.Key,
Value: data.Value,
}
}
return metadata
}
func ListUserMetadataToDomain(req *auth.ListMyMetadataRequest) *domain.MetadataSearchRequest {
offset, limit, asc := object.ListQueryToModel(req.Query)
return &domain.MetadataSearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
Queries: metadata.MetadataQueriesToModel(req.Queries),
}
}

View File

@@ -6,9 +6,12 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/change"
"github.com/caos/zitadel/internal/api/grpc/metadata"
"github.com/caos/zitadel/internal/api/grpc/object"
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/org"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1/models"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
@@ -37,6 +40,79 @@ func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserC
}, nil
}
func (s *Server) ListMyMetadata(ctx context.Context, req *auth_pb.ListMyMetadataRequest) (*auth_pb.ListMyMetadataResponse, error) {
res, err := s.repo.SearchMyMetadata(ctx, ListUserMetadataToDomain(req))
if err != nil {
return nil, err
}
return &auth_pb.ListMyMetadataResponse{
Result: metadata.MetadataListToPb(res.Result),
Details: obj_grpc.ToListDetails(
res.TotalResult,
res.Sequence,
res.Timestamp,
),
}, nil
}
func (s *Server) GetMyMetadata(ctx context.Context, req *auth_pb.GetMyMetadataRequest) (*auth_pb.GetMyMetadataResponse, error) {
data, err := s.repo.GetMyMetadataByKey(ctx, req.Key)
if err != nil {
return nil, err
}
return &auth_pb.GetMyMetadataResponse{
Metadata: metadata.DomainMetadataToPb(data),
}, nil
}
func (s *Server) SetMyMetadata(ctx context.Context, req *auth_pb.SetMyMetadataRequest) (*auth_pb.SetMyMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.SetUserMetadata(ctx, &domain.Metadata{Key: req.Key, Value: req.Value}, ctxData.UserID, ctxData.ResourceOwner)
if err != nil {
return nil, err
}
return &auth_pb.SetMyMetadataResponse{
Details: obj_grpc.AddToDetailsPb(
result.Sequence,
result.ChangeDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) BulkSetMyMetadata(ctx context.Context, req *auth_pb.BulkSetMyMetadataRequest) (*auth_pb.BulkSetMyMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.BulkSetUserMetadata(ctx, ctxData.UserID, ctxData.ResourceOwner, BulkSetMetadataToDomain(req)...)
if err != nil {
return nil, err
}
return &auth_pb.BulkSetMyMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) RemoveMyMetadata(ctx context.Context, req *auth_pb.RemoveMyMetadataRequest) (*auth_pb.RemoveMyMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.RemoveUserMetadata(ctx, req.Key, ctxData.UserID, ctxData.ResourceOwner)
if err != nil {
return nil, err
}
return &auth_pb.RemoveMyMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) BulkRemoveMyMetadata(ctx context.Context, req *auth_pb.BulkRemoveMyMetadataRequest) (*auth_pb.BulkRemoveMyMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.BulkRemoveUserMetadata(ctx, ctxData.UserID, ctxData.ResourceOwner, req.Keys...)
if err != nil {
return nil, err
}
return &auth_pb.BulkRemoveMyMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) ListMyUserSessions(ctx context.Context, req *auth_pb.ListMyUserSessionsRequest) (*auth_pb.ListMyUserSessionsResponse, error) {
userSessions, err := s.repo.GetMyUserSessions(ctx)
if err != nil {

View File

@@ -9,10 +9,12 @@ import (
"github.com/caos/zitadel/internal/api/grpc/authn"
change_grpc "github.com/caos/zitadel/internal/api/grpc/change"
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
"github.com/caos/zitadel/internal/api/grpc/metadata"
"github.com/caos/zitadel/internal/api/grpc/object"
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/api/grpc/user"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
@@ -78,6 +80,79 @@ func (s *Server) IsUserUnique(ctx context.Context, req *mgmt_pb.IsUserUniqueRequ
}, nil
}
func (s *Server) ListUserMetadata(ctx context.Context, req *mgmt_pb.ListUserMetadataRequest) (*mgmt_pb.ListUserMetadataResponse, error) {
res, err := s.user.SearchMetadata(ctx, req.Id, authz.GetCtxData(ctx).OrgID, ListUserMetadataToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.ListUserMetadataResponse{
Result: metadata.MetadataListToPb(res.Result),
Details: obj_grpc.ToListDetails(
res.TotalResult,
res.Sequence,
res.Timestamp,
),
}, nil
}
func (s *Server) GetUserMetadata(ctx context.Context, req *mgmt_pb.GetUserMetadataRequest) (*mgmt_pb.GetUserMetadataResponse, error) {
data, err := s.user.GetMetadataByKey(ctx, req.Id, authz.GetCtxData(ctx).OrgID, req.Key)
if err != nil {
return nil, err
}
return &mgmt_pb.GetUserMetadataResponse{
Metadata: metadata.DomainMetadataToPb(data),
}, nil
}
func (s *Server) SetUserMetadata(ctx context.Context, req *mgmt_pb.SetUserMetadataRequest) (*mgmt_pb.SetUserMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.SetUserMetadata(ctx, &domain.Metadata{Key: req.Key, Value: req.Value}, req.Id, ctxData.ResourceOwner)
if err != nil {
return nil, err
}
return &mgmt_pb.SetUserMetadataResponse{
Details: obj_grpc.AddToDetailsPb(
result.Sequence,
result.ChangeDate,
result.ResourceOwner,
),
}, nil
}
func (s *Server) BulkSetUserMetadata(ctx context.Context, req *mgmt_pb.BulkSetUserMetadataRequest) (*mgmt_pb.BulkSetUserMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.BulkSetUserMetadata(ctx, req.Id, ctxData.ResourceOwner, BulkSetMetadataToDomain(req)...)
if err != nil {
return nil, err
}
return &mgmt_pb.BulkSetUserMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) RemoveUserMetadata(ctx context.Context, req *mgmt_pb.RemoveUserMetadataRequest) (*mgmt_pb.RemoveUserMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.RemoveUserMetadata(ctx, req.Key, req.Id, ctxData.ResourceOwner)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveUserMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRemoveUserMetadataRequest) (*mgmt_pb.BulkRemoveUserMetadataResponse, error) {
ctxData := authz.GetCtxData(ctx)
result, err := s.command.BulkRemoveUserMetadata(ctx, req.Id, ctxData.ResourceOwner, req.Keys...)
if err != nil {
return nil, err
}
return &mgmt_pb.BulkRemoveUserMetadataResponse{
Details: obj_grpc.DomainToChangeDetailsPb(result),
}, nil
}
func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) {
human, err := s.command.AddHuman(ctx, authz.GetCtxData(ctx).OrgID, AddHumanUserRequestToDomain(req))
if err != nil {

View File

@@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/authn"
"github.com/caos/zitadel/internal/api/grpc/metadata"
"github.com/caos/zitadel/internal/api/grpc/object"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain"
@@ -38,6 +39,27 @@ func ListUsersRequestToModel(ctx context.Context, req *mgmt_pb.ListUsersRequest)
}
}
func BulkSetMetadataToDomain(req *mgmt_pb.BulkSetUserMetadataRequest) []*domain.Metadata {
metadata := make([]*domain.Metadata, len(req.Metadata))
for i, data := range req.Metadata {
metadata[i] = &domain.Metadata{
Key: data.Key,
Value: data.Value,
}
}
return metadata
}
func ListUserMetadataToDomain(req *mgmt_pb.ListUserMetadataRequest) *domain.MetadataSearchRequest {
offset, limit, asc := object.ListQueryToModel(req.Query)
return &domain.MetadataSearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
Queries: metadata.MetadataQueriesToModel(req.Queries),
}
}
func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human {
h := &domain.Human{
Username: req.UserName,

View File

@@ -0,0 +1,53 @@
package metadata
import (
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/domain"
meta_pb "github.com/caos/zitadel/pkg/grpc/metadata"
)
func MetadataListToPb(dataList []*domain.Metadata) []*meta_pb.Metadata {
mds := make([]*meta_pb.Metadata, len(dataList))
for i, data := range dataList {
mds[i] = DomainMetadataToPb(data)
}
return mds
}
func DomainMetadataToPb(data *domain.Metadata) *meta_pb.Metadata {
return &meta_pb.Metadata{
Key: data.Key,
Value: data.Value,
Details: object.ToViewDetailsPb(
data.Sequence,
data.CreationDate,
data.ChangeDate,
data.ResourceOwner,
),
}
}
func MetadataQueriesToModel(queries []*meta_pb.MetadataQuery) []*domain.MetadataSearchQuery {
q := make([]*domain.MetadataSearchQuery, len(queries))
for i, query := range queries {
q[i] = MetadataQueryToModel(query)
}
return q
}
func MetadataQueryToModel(query *meta_pb.MetadataQuery) *domain.MetadataSearchQuery {
switch q := query.Query.(type) {
case *meta_pb.MetadataQuery_KeyQuery:
return MetadataKeyQueryToModel(q.KeyQuery)
default:
return nil
}
}
func MetadataKeyQueryToModel(q *meta_pb.MetadataKeyQuery) *domain.MetadataSearchQuery {
return &domain.MetadataSearchQuery{
Key: domain.MetadataSearchKeyKey,
Method: object.TextMethodToModel(q.Method),
Value: q.Key,
}
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
key_model "github.com/caos/zitadel/internal/key/model"
key_view_model "github.com/caos/zitadel/internal/key/repository/view/model"
"github.com/caos/zitadel/internal/telemetry/tracing"
@@ -296,3 +297,39 @@ func (r *UserRepo) getUserEvents(ctx context.Context, userID string, sequence ui
}
return r.Eventstore.FilterEvents(ctx, query)
}
func (repo *UserRepo) GetMyMetadataByKey(ctx context.Context, key string) (*domain.Metadata, error) {
ctxData := authz.GetCtxData(ctx)
data, err := repo.View.MetadataByKeyAndResourceOwner(ctxData.UserID, ctxData.ResourceOwner, key)
if err != nil {
return nil, err
}
return iam_model.MetadataViewToDomain(data), nil
}
func (repo *UserRepo) SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
ctxData := authz.GetCtxData(ctx)
err := req.EnsureLimit(repo.SearchLimit)
if err != nil {
return nil, err
}
sequence, sequenceErr := repo.View.GetLatestUserSequence()
logging.Log("EVENT-N9fsd").OnError(sequenceErr).Warn("could not read latest user sequence")
req.AppendAggregateIDQuery(ctxData.UserID)
req.AppendResourceOwnerQuery(ctxData.ResourceOwner)
metadata, count, err := repo.View.SearchMetadata(req)
if err != nil {
return nil, err
}
result := &domain.MetadataSearchResponse{
Offset: req.Offset,
Limit: req.Limit,
TotalResult: count,
Result: iam_model.MetadataViewsToDomain(metadata),
}
if sequenceErr == nil {
result.Sequence = sequence.CurrentSequence
result.Timestamp = sequence.LastSuccessfulSpoolerRun
}
return result, nil
}

View File

@@ -72,6 +72,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newRefreshToken(handler{view, bulkLimit, configs.cycleDuration("RefreshToken"), errorCount, es}),
newPrivacyPolicy(handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}),
newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
}
}

View File

@@ -0,0 +1,124 @@
package handler
import (
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
type Metadata struct {
handler
subscription *v1.Subscription
}
func newMetadata(handler handler) *Metadata {
h := &Metadata{
handler: handler,
}
h.subscribe()
return h
}
func (m *Metadata) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
metadataTable = "auth.metadata"
)
func (m *Metadata) ViewModel() string {
return metadataTable
}
func (m *Metadata) Subscription() *v1.Subscription {
return m.subscription
}
func (_ *Metadata) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{usr_model.UserAggregate}
}
func (p *Metadata) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMetadataSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *Metadata) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMetadataSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *Metadata) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case usr_model.UserAggregate:
err = m.processMetadata(event)
}
return err
}
func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
metadata := new(iam_model.MetadataView)
switch event.Type {
case usr_model.UserMetadataSet:
err = metadata.SetData(event)
if err != nil {
return err
}
metadata, err = m.view.MetadataByKey(event.AggregateID, metadata.Key)
if err != nil && !caos_errs.IsNotFound(err) {
return err
}
if caos_errs.IsNotFound(err) {
err = nil
metadata = new(iam_model.MetadataView)
metadata.CreationDate = event.CreationDate
}
err = metadata.AppendEvent(event)
case usr_model.UserMetadataRemoved:
data := new(iam_model.MetadataView)
err = data.SetData(event)
if err != nil {
return err
}
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
case usr_model.UserRemoved:
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
default:
return m.view.ProcessedMetadataSequence(event)
}
if err != nil {
return err
}
return m.view.PutMetadata(metadata, event)
}
func (m *Metadata) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-miJJs", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler")
return spooler.HandleError(event, err, m.view.GetLatestMetadataFailedEvent, m.view.ProcessedMetadataFailedEvent, m.view.ProcessedMetadataSequence, m.errorCountUntilSkip)
}
func (m *Metadata) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateMetadataSpoolerRunTimestamp)
}

View File

@@ -0,0 +1,73 @@
package view
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
metadataTable = "auth.metadata"
)
func (v *View) MetadataByKey(aggregateID, key string) (*model.MetadataView, error) {
return view.MetadataByKey(v.Db, metadataTable, aggregateID, key)
}
func (v *View) MetadataListByAggregateID(aggregateID string) ([]*model.MetadataView, error) {
return view.GetMetadataList(v.Db, metadataTable, aggregateID)
}
func (v *View) MetadataByKeyAndResourceOwner(aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
return view.MetadataByKeyAndResourceOwner(v.Db, metadataTable, aggregateID, resourceOwner, key)
}
func (v *View) SearchMetadata(request *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
return view.SearchMetadata(v.Db, metadataTable, request)
}
func (v *View) PutMetadata(template *model.MetadataView, event *models.Event) error {
err := view.PutMetadata(v.Db, metadataTable, template)
if err != nil {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) DeleteMetadata(aggregateID, key string, event *models.Event) error {
err := view.DeleteMetadata(v.Db, metadataTable, aggregateID, key)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) DeleteMetadataByAggregateID(aggregateID string, event *models.Event) error {
err := view.DeleteMetadataByAggregateID(v.Db, metadataTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) GetLatestMetadataSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(metadataTable)
}
func (v *View) ProcessedMetadataSequence(event *models.Event) error {
return v.saveCurrentSequence(metadataTable, event)
}
func (v *View) UpdateMetadataSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(metadataTable)
}
func (v *View) GetLatestMetadataFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(metadataTable, sequence)
}
func (v *View) ProcessedMetadataFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
key_model "github.com/caos/zitadel/internal/key/model"
"github.com/caos/zitadel/internal/user/model"
@@ -42,4 +43,7 @@ type myUserRepo interface {
MyUserChanges(ctx context.Context, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.UserChanges, error)
SearchMyUserMemberships(ctx context.Context, request *model.UserMembershipSearchRequest) (*model.UserMembershipSearchResponse, error)
GetMyMetadataByKey(ctx context.Context, key string) (*domain.Metadata, error)
SearchMyMetadata(ctx context.Context, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error)
}

View File

@@ -0,0 +1,49 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/metadata"
)
type MetadataWriteModel struct {
eventstore.WriteModel
Key string
Value []byte
State domain.MetadataState
}
func (wm *MetadataWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *metadata.SetEvent:
if wm.Key != e.Key {
continue
}
wm.Value = e.Value
wm.State = domain.MetadataStateActive
case *metadata.RemovedEvent:
wm.State = domain.MetadataStateRemoved
}
}
return wm.WriteModel.Reduce()
}
type MetadataListWriteModel struct {
eventstore.WriteModel
metadataList map[string][]byte
}
func (wm *MetadataListWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *metadata.SetEvent:
wm.metadataList[e.Key] = e.Value
case *metadata.RemovedEvent:
delete(wm.metadataList, e.Key)
}
}
return wm.WriteModel.Reduce()
}

View File

@@ -153,3 +153,12 @@ func writeModelToPasswordlessInitCode(initCodeModel *HumanPasswordlessInitCodeWr
State: initCodeModel.State,
}
}
func writeModelToUserMetadata(wm *UserMetadataWriteModel) *domain.Metadata {
return &domain.Metadata{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Key: wm.Key,
Value: wm.Value,
State: wm.State,
}
}

View File

@@ -0,0 +1,177 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
func (c *Commands) SetUserMetadata(ctx context.Context, metadata *domain.Metadata, userID, resourceOwner string) (_ *domain.Metadata, err error) {
err = c.checkUserExists(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
setMetadata := NewUserMetadataWriteModel(userID, resourceOwner, metadata.Key)
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
event, err := c.setUserMetadata(ctx, userAgg, metadata)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(setMetadata, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToUserMetadata(setMetadata), nil
}
func (c *Commands) BulkSetUserMetadata(ctx context.Context, userID, resourceOwner string, metadatas ...*domain.Metadata) (_ *domain.ObjectDetails, err error) {
if len(metadatas) == 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
}
err = c.checkUserExists(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
events := make([]eventstore.EventPusher, len(metadatas))
setMetadata := NewUserMetadataListWriteModel(userID, resourceOwner)
userAgg := UserAggregateFromWriteModel(&setMetadata.WriteModel)
for i, data := range metadatas {
event, err := c.setUserMetadata(ctx, userAgg, data)
if err != nil {
return nil, err
}
events[i] = event
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(setMetadata, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&setMetadata.WriteModel), nil
}
func (c *Commands) setUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadata *domain.Metadata) (pusher eventstore.EventPusher, err error) {
if !metadata.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "META-2m00f", "Errors.Metadata.Invalid")
}
return user.NewMetadataSetEvent(
ctx,
userAgg,
metadata.Key,
metadata.Value,
), nil
}
func (c *Commands) RemoveUserMetadata(ctx context.Context, metadataKey, userID, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if metadataKey == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "META-2n0fs", "Errors.Metadata.Invalid")
}
err = c.checkUserExists(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
removeMetadata, err := c.getUserMetadataModelByID(ctx, userID, resourceOwner, metadataKey)
if err != nil {
return nil, err
}
if !removeMetadata.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "META-ncnw3", "Errors.Metadata.NotFound")
}
userAgg := UserAggregateFromWriteModel(&removeMetadata.WriteModel)
event, err := c.removeUserMetadata(ctx, userAgg, metadataKey)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(removeMetadata, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
}
func (c *Commands) BulkRemoveUserMetadata(ctx context.Context, userID, resourceOwner string, metadataKeys ...string) (_ *domain.ObjectDetails, err error) {
if len(metadataKeys) == 0 {
return nil, caos_errs.ThrowPreconditionFailed(nil, "META-9mm2d", "Errors.Metadata.NoData")
}
err = c.checkUserExists(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
events := make([]eventstore.EventPusher, len(metadataKeys))
removeMetadata, err := c.getUserMetadataListModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&removeMetadata.WriteModel)
for i, key := range metadataKeys {
if key == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-m29ds", "Errors.Metadata.Invalid")
}
if _, found := removeMetadata.metadataList[key]; !found {
return nil, caos_errs.ThrowNotFound(nil, "META-2nnds", "Errors.Metadata.KeyNotExisting")
}
event, err := c.removeUserMetadata(ctx, userAgg, key)
if err != nil {
return nil, err
}
events[i] = event
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(removeMetadata, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&removeMetadata.WriteModel), nil
}
func (c *Commands) removeUserMetadata(ctx context.Context, userAgg *eventstore.Aggregate, metadataKey string) (pusher eventstore.EventPusher, err error) {
pusher = user.NewMetadataRemovedEvent(
ctx,
userAgg,
metadataKey,
)
return pusher, nil
}
func (c *Commands) getUserMetadataModelByID(ctx context.Context, userID, resourceOwner, key string) (*UserMetadataWriteModel, error) {
userMetadataWriteModel := NewUserMetadataWriteModel(userID, resourceOwner, key)
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
if err != nil {
return nil, err
}
return userMetadataWriteModel, nil
}
func (c *Commands) getUserMetadataListModelByID(ctx context.Context, userID, resourceOwner string) (*UserMetadataListWriteModel, error) {
userMetadataWriteModel := NewUserMetadataListWriteModel(userID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, userMetadataWriteModel)
if err != nil {
return nil, err
}
return userMetadataWriteModel, nil
}

View File

@@ -0,0 +1,92 @@
package command
import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
type UserMetadataWriteModel struct {
MetadataWriteModel
}
func NewUserMetadataWriteModel(userID, resourceOwner, key string) *UserMetadataWriteModel {
return &UserMetadataWriteModel{
MetadataWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
Key: key,
},
}
}
func (wm *UserMetadataWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.MetadataSetEvent:
wm.MetadataWriteModel.AppendEvents(&e.SetEvent)
case *user.MetadataRemovedEvent:
wm.MetadataWriteModel.AppendEvents(&e.RemovedEvent)
}
}
}
func (wm *UserMetadataWriteModel) Reduce() error {
return wm.MetadataWriteModel.Reduce()
}
func (wm *UserMetadataWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateIDs(wm.MetadataWriteModel.AggregateID).
AggregateTypes(user.AggregateType).
EventTypes(
user.MetadataSetType,
user.MetadataRemovedType).
Builder()
}
type UserMetadataListWriteModel struct {
MetadataListWriteModel
}
func NewUserMetadataListWriteModel(userID, resourceOwner string) *UserMetadataListWriteModel {
return &UserMetadataListWriteModel{
MetadataListWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
metadataList: make(map[string][]byte),
},
}
}
func (wm *UserMetadataListWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.MetadataSetEvent:
wm.MetadataListWriteModel.AppendEvents(&e.SetEvent)
case *user.MetadataRemovedEvent:
wm.MetadataListWriteModel.AppendEvents(&e.RemovedEvent)
}
}
}
func (wm *UserMetadataListWriteModel) Reduce() error {
return wm.MetadataListWriteModel.Reduce()
}
func (wm *UserMetadataListWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateIDs(wm.MetadataListWriteModel.AggregateID).
AggregateTypes(user.AggregateType).
EventTypes(
user.MetadataSetType,
user.MetadataRemovedType).
Builder()
}

View File

@@ -0,0 +1,739 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_SetMetadata(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
metadata *domain.Metadata
}
)
type res struct {
want *domain.Metadata
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user not existing, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadata: &domain.Metadata{
Key: "key",
Value: []byte("value"),
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid metadata, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadata: &domain.Metadata{
Key: "key",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "add metadata, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadata: &domain.Metadata{
Key: "key",
Value: []byte("value"),
},
},
res: res{
want: &domain.Metadata{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Key: "key",
Value: []byte("value"),
State: domain.MetadataStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.SetUserMetadata(tt.args.ctx, tt.args.metadata, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_BulkSetMetadata(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
metadataList []*domain.Metadata
}
)
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "empty meta data list, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "user not existing, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []*domain.Metadata{
{Key: "key", Value: []byte("value")},
{Key: "key1", Value: []byte("value1")},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid metadata, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []*domain.Metadata{
{Key: "key"},
{Key: "key1"},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "add metadata, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key1",
[]byte("value1"),
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []*domain.Metadata{
{Key: "key", Value: []byte("value")},
{Key: "key1", Value: []byte("value1")},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.BulkSetUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_UserRemoveMetadata(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
metadataKey string
}
)
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user not existing, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataKey: "key",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "invalid metadata, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataKey: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "meta data not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataKey: "key",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove metadata, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewMetadataRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataKey: "key",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveUserMetadata(tt.args.ctx, tt.args.metadataKey, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_BulkRemoveMetadata(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
metadataList []string
}
)
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "empty meta data list, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "user not existing, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []string{"key", "key1"},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "remove metadata keys not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []string{"key", "key1"},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "invalid metadata, pre condition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key1",
[]byte("value1"),
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []string{""},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "remove metadata, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"",
"firstname lastname",
language.Und,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
[]byte("value"),
),
),
eventFromEventPusher(
user.NewMetadataSetEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key1",
[]byte("value1"),
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewMetadataRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key",
),
),
eventFromEventPusher(
user.NewMetadataRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"key1",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
metadataList: []string{"key", "key1"},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.BulkRemoveUserMetadata(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.metadataList...)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}

View File

@@ -0,0 +1,83 @@
package domain
import (
"time"
caos_errors "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
)
type Metadata struct {
es_models.ObjectRoot
State MetadataState
Key string
Value []byte
}
type MetadataState int32
const (
MetadataStateUnspecified MetadataState = iota
MetadataStateActive
MetadataStateRemoved
)
func (m *Metadata) IsValid() bool {
return m.Key != "" && len(m.Value) > 0
}
func (s MetadataState) Exists() bool {
return s != MetadataStateUnspecified && s != MetadataStateRemoved
}
type MetadataSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MetadataSearchKey
Asc bool
Queries []*MetadataSearchQuery
}
type MetadataSearchKey int32
const (
MetadataSearchKeyUnspecified MetadataSearchKey = iota
MetadataSearchKeyAggregateID
MetadataSearchKeyResourceOwner
MetadataSearchKeyKey
MetadataSearchKeyValue
)
type MetadataSearchQuery struct {
Key MetadataSearchKey
Method SearchMethod
Value interface{}
}
type MetadataSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*Metadata
Sequence uint64
Timestamp time.Time
}
func (r *MetadataSearchRequest) EnsureLimit(limit uint64) error {
if r.Limit > limit {
return caos_errors.ThrowInvalidArgument(nil, "SEARCH-0ds32", "Errors.Limit.ExceedsDefault")
}
if r.Limit == 0 {
r.Limit = limit
}
return nil
}
func (r *MetadataSearchRequest) AppendAggregateIDQuery(aggregateID string) {
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyAggregateID, Method: SearchMethodEquals, Value: aggregateID})
}
func (r *MetadataSearchRequest) AppendResourceOwnerQuery(resourceOwner string) {
r.Queries = append(r.Queries, &MetadataSearchQuery{Key: MetadataSearchKeyResourceOwner, Method: SearchMethodEquals, Value: resourceOwner})
}

View File

@@ -0,0 +1,80 @@
package view
import (
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/view/repository"
)
func GetMetadataList(db *gorm.DB, table string, aggregateID string) ([]*model.MetadataView, error) {
metadatas := make([]*model.MetadataView, 0)
queries := []*domain.MetadataSearchQuery{
{
Key: domain.MetadataSearchKeyAggregateID,
Value: aggregateID,
Method: domain.SearchMethodEquals,
},
}
query := repository.PrepareSearchQuery(table, model.MetadataSearchRequest{Queries: queries})
_, err := query(db, &metadatas)
if err != nil {
return nil, err
}
return metadatas, nil
}
func MetadataByKey(db *gorm.DB, table, aggregateID, key string) (*model.MetadataView, error) {
metadata := new(model.MetadataView)
aggregateIDQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
keyQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyKey, Value: key, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, keyQuery)
err := query(db, metadata)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-29kkd", "Errors.Metadata.NotExisting")
}
return metadata, err
}
func MetadataByKeyAndResourceOwner(db *gorm.DB, table, aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
metadata := new(model.MetadataView)
aggregateIDQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyAggregateID, Value: aggregateID, Method: domain.SearchMethodEquals}
resourceOwnerQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyResourceOwner, Value: resourceOwner, Method: domain.SearchMethodEquals}
keyQuery := &model.MetadataSearchQuery{Key: domain.MetadataSearchKeyKey, Value: key, Method: domain.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, resourceOwnerQuery, keyQuery)
err := query(db, metadata)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-29kkd", "Errors.Metadata.NotExisting")
}
return metadata, err
}
func SearchMetadata(db *gorm.DB, table string, req *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
metadata := make([]*model.MetadataView, 0)
query := repository.PrepareSearchQuery(table, model.MetadataSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &metadata)
if err != nil {
return nil, 0, err
}
return metadata, count, nil
}
func PutMetadata(db *gorm.DB, table string, customText *model.MetadataView) error {
save := repository.PrepareSave(table)
return save(db, customText)
}
func DeleteMetadata(db *gorm.DB, table, aggregateID, key string) error {
aggregateIDQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyAggregateID), Value: aggregateID}
keyQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyKey), Value: key}
delete := repository.PrepareDeleteByKeys(table, aggregateIDQuery, keyQuery)
return delete(db)
}
func DeleteMetadataByAggregateID(db *gorm.DB, table, aggregateID string) error {
aggregateIDQuery := repository.Key{Key: model.MetadataSearchKey(domain.MetadataSearchKeyAggregateID), Value: aggregateID}
delete := repository.PrepareDeleteByKeys(table, aggregateIDQuery)
return delete(db)
}

View File

@@ -30,10 +30,10 @@ type CustomTextView struct {
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
Template string `json:"Template" gorm:"column:template;primary_key"`
Language string `json:"Language" gorm:"column:language;primary_key"`
Key string `json:"Key" gorm:"column:key;primary_key"`
Text string `json:"Text" gorm:"column:text"`
Template string `json:"template" gorm:"column:template;primary_key"`
Language string `json:"language" gorm:"column:language;primary_key"`
Key string `json:"key" gorm:"column:key;primary_key"`
Text string `json:"text" gorm:"column:text"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}

View File

@@ -0,0 +1,79 @@
package model
import (
"encoding/json"
"time"
"github.com/caos/zitadel/internal/domain"
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
)
const (
MetadataKeyAggregateID = "aggregate_id"
MetadataKeyResourceOwner = "resource_owner"
MetadataKeyKey = "key"
MetadataKeyValue = "value"
)
type MetadataView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
ResourceOwner string `json:"-" gorm:"column:resource_owner"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
Key string `json:"key" gorm:"column:key;primary_key"`
Value []byte `json:"value" gorm:"column:value"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func MetadataViewsToDomain(texts []*MetadataView) []*domain.Metadata {
result := make([]*domain.Metadata, len(texts))
for i, text := range texts {
result[i] = MetadataViewToDomain(text)
}
return result
}
func MetadataViewToDomain(data *MetadataView) *domain.Metadata {
return &domain.Metadata{
ObjectRoot: models.ObjectRoot{
AggregateID: data.AggregateID,
Sequence: data.Sequence,
CreationDate: data.CreationDate,
ChangeDate: data.ChangeDate,
},
Key: data.Key,
Value: data.Value,
}
}
func (md *MetadataView) AppendEvent(event *models.Event) (err error) {
md.Sequence = event.Sequence
switch event.Type {
case usr_model.UserMetadataSet:
md.setRootData(event)
err = md.SetData(event)
}
return err
}
func (md *MetadataView) setRootData(event *models.Event) {
md.AggregateID = event.AggregateID
md.ResourceOwner = event.ResourceOwner
md.ChangeDate = event.CreationDate
md.Sequence = event.Sequence
}
func (md *MetadataView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, md); err != nil {
logging.Log("MODEL-3n9fs").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
}
return nil
}

View File

@@ -0,0 +1,64 @@
package model
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/view/repository"
)
type MetadataSearchRequest domain.MetadataSearchRequest
type MetadataSearchQuery domain.MetadataSearchQuery
type MetadataSearchKey domain.MetadataSearchKey
func (req MetadataSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req MetadataSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req MetadataSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == domain.MetadataSearchKeyUnspecified {
return nil
}
return MetadataSearchKey(req.SortingColumn)
}
func (req MetadataSearchRequest) GetAsc() bool {
return req.Asc
}
func (req MetadataSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = MetadataSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req MetadataSearchQuery) GetKey() repository.ColumnKey {
return MetadataSearchKey(req.Key)
}
func (req MetadataSearchQuery) GetMethod() domain.SearchMethod {
return req.Method
}
func (req MetadataSearchQuery) GetValue() interface{} {
return req.Value
}
func (key MetadataSearchKey) ToColumnName() string {
switch domain.MetadataSearchKey(key) {
case domain.MetadataSearchKeyAggregateID:
return MetadataKeyAggregateID
case domain.MetadataSearchKeyResourceOwner:
return MetadataKeyResourceOwner
case domain.MetadataSearchKeyKey:
return MetadataKeyKey
case domain.MetadataSearchKeyValue:
return MetadataKeyValue
default:
return ""
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/domain"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
usr_view "github.com/caos/zitadel/internal/user/repository/view"
"github.com/caos/logging"
@@ -128,6 +129,40 @@ func (repo *UserRepo) IsUserUnique(ctx context.Context, userName, email string)
return repo.View.IsUserUnique(userName, email)
}
func (repo *UserRepo) GetMetadataByKey(ctx context.Context, userID, resourceOwner, key string) (*domain.Metadata, error) {
data, err := repo.View.MetadataByKeyAndResourceOwner(userID, resourceOwner, key)
if err != nil {
return nil, err
}
return iam_model.MetadataViewToDomain(data), nil
}
func (repo *UserRepo) SearchMetadata(ctx context.Context, userID, resourceOwner string, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error) {
err := req.EnsureLimit(repo.SearchLimit)
if err != nil {
return nil, err
}
sequence, sequenceErr := repo.View.GetLatestUserSequence()
logging.Log("EVENT-m0ds3").OnError(sequenceErr).Warn("could not read latest user sequence")
req.AppendAggregateIDQuery(userID)
req.AppendResourceOwnerQuery(resourceOwner)
metadata, count, err := repo.View.SearchMetadata(req)
if err != nil {
return nil, err
}
result := &domain.MetadataSearchResponse{
Offset: req.Offset,
Limit: req.Limit,
TotalResult: count,
Result: iam_model.MetadataViewsToDomain(metadata),
}
if sequenceErr == nil {
result.Sequence = sequence.CurrentSequence
result.Timestamp = sequence.LastSuccessfulSpoolerRun
}
return result, nil
}
func (repo *UserRepo) UserMFAs(ctx context.Context, userID string) ([]*usr_model.MultiFactor, error) {
user, err := repo.UserByID(ctx, userID)
if err != nil {

View File

@@ -85,6 +85,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("PrivacyPolicy"), errorCount, es}),
newCustomText(
handler{view, bulkLimit, configs.cycleDuration("CustomText"), errorCount, es}),
newMetadata(
handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}),
}
}

View File

@@ -0,0 +1,124 @@
package handler
import (
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
type Metadata struct {
handler
subscription *v1.Subscription
}
func newMetadata(handler handler) *Metadata {
h := &Metadata{
handler: handler,
}
h.subscribe()
return h
}
func (m *Metadata) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
metadataTable = "management.metadata"
)
func (m *Metadata) ViewModel() string {
return metadataTable
}
func (m *Metadata) Subscription() *v1.Subscription {
return m.subscription
}
func (_ *Metadata) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{usr_model.UserAggregate}
}
func (p *Metadata) CurrentSequence() (uint64, error) {
sequence, err := p.view.GetLatestMetadataSequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *Metadata) EventQuery() (*es_models.SearchQuery, error) {
sequence, err := m.view.GetLatestMetadataSequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *Metadata) Reduce(event *es_models.Event) (err error) {
switch event.AggregateType {
case usr_model.UserAggregate:
err = m.processMetadata(event)
}
return err
}
func (m *Metadata) processMetadata(event *es_models.Event) (err error) {
metadata := new(iam_model.MetadataView)
switch event.Type {
case usr_model.UserMetadataSet:
err = metadata.SetData(event)
if err != nil {
return err
}
metadata, err = m.view.MetadataByKey(event.AggregateID, metadata.Key)
if err != nil && !caos_errs.IsNotFound(err) {
return err
}
if caos_errs.IsNotFound(err) {
err = nil
metadata = new(iam_model.MetadataView)
metadata.CreationDate = event.CreationDate
}
err = metadata.AppendEvent(event)
case usr_model.UserMetadataRemoved:
data := new(iam_model.MetadataView)
err = data.SetData(event)
if err != nil {
return err
}
return m.view.DeleteMetadata(event.AggregateID, data.Key, event)
case usr_model.UserRemoved:
return m.view.DeleteMetadataByAggregateID(event.AggregateID, event)
default:
return m.view.ProcessedMetadataSequence(event)
}
if err != nil {
return err
}
return m.view.PutMetadata(metadata, event)
}
func (m *Metadata) OnError(event *es_models.Event, err error) error {
logging.LogWithFields("SPOOL-3m912", "id", event.AggregateID).WithError(err).Warn("something went wrong in custom text handler")
return spooler.HandleError(event, err, m.view.GetLatestMetadataFailedEvent, m.view.ProcessedMetadataFailedEvent, m.view.ProcessedMetadataSequence, m.errorCountUntilSkip)
}
func (o *Metadata) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMetadataSpoolerRunTimestamp)
}

View File

@@ -0,0 +1,73 @@
package view
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
metadataTable = "management.metadata"
)
func (v *View) MetadataByKey(aggregateID, key string) (*model.MetadataView, error) {
return view.MetadataByKey(v.Db, metadataTable, aggregateID, key)
}
func (v *View) MetadataByKeyAndResourceOwner(aggregateID, resourceOwner, key string) (*model.MetadataView, error) {
return view.MetadataByKeyAndResourceOwner(v.Db, metadataTable, aggregateID, resourceOwner, key)
}
func (v *View) MetadataListByAggregateID(aggregateID string) ([]*model.MetadataView, error) {
return view.GetMetadataList(v.Db, metadataTable, aggregateID)
}
func (v *View) SearchMetadata(request *domain.MetadataSearchRequest) ([]*model.MetadataView, uint64, error) {
return view.SearchMetadata(v.Db, metadataTable, request)
}
func (v *View) PutMetadata(template *model.MetadataView, event *models.Event) error {
err := view.PutMetadata(v.Db, metadataTable, template)
if err != nil {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) DeleteMetadata(aggregateID, key string, event *models.Event) error {
err := view.DeleteMetadata(v.Db, metadataTable, aggregateID, key)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) DeleteMetadataByAggregateID(aggregateID string, event *models.Event) error {
err := view.DeleteMetadataByAggregateID(v.Db, metadataTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMetadataSequence(event)
}
func (v *View) GetLatestMetadataSequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(metadataTable)
}
func (v *View) ProcessedMetadataSequence(event *models.Event) error {
return v.saveCurrentSequence(metadataTable, event)
}
func (v *View) UpdateMetadataSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(metadataTable)
}
func (v *View) GetLatestMetadataFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(metadataTable, sequence)
}
func (v *View) ProcessedMetadataFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
key_model "github.com/caos/zitadel/internal/key/model"
"github.com/caos/zitadel/internal/user/model"
)
@@ -16,6 +17,9 @@ type UserRepository interface {
GetUserByLoginNameGlobal(ctx context.Context, email string) (*model.UserView, error)
IsUserUnique(ctx context.Context, userName, email string) (bool, error)
GetMetadataByKey(ctx context.Context, userID, resourceOwner, key string) (*domain.Metadata, error)
SearchMetadata(ctx context.Context, userID, resourceOwner string, req *domain.MetadataSearchRequest) (*domain.MetadataSearchResponse, error)
UserChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool, retention time.Duration) (*model.UserChanges, error)
ProfileByID(ctx context.Context, userID string) (*model.Profile, error)

View File

@@ -0,0 +1,92 @@
package metadata
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
)
const (
SetEventType = "metadata.set"
RemovedEventType = "metadata.removed"
)
type SetEvent struct {
eventstore.BaseEvent `json:"-"`
Key string `json:"key"`
Value []byte `json:"value"`
}
func (e *SetEvent) Data() interface{} {
return e
}
func (e *SetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewSetEvent(
base *eventstore.BaseEvent,
key string,
value []byte,
) *SetEvent {
return &SetEvent{
BaseEvent: *base,
Key: key,
Value: value,
}
}
func SetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &SetEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "META-3n9fs", "unable to unmarshal metadata set")
}
return e, nil
}
type RemovedEvent struct {
eventstore.BaseEvent `json:"-"`
Key string `json:"key"`
}
func (e *RemovedEvent) Data() interface{} {
return e
}
func (e *RemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewRemovedEvent(
base *eventstore.BaseEvent,
key string,
) *RemovedEvent {
return &RemovedEvent{
BaseEvent: *base,
Key: key,
}
}
func RemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &RemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "META-2m99f", "unable to unmarshal metadata removed")
}
return e, nil
}

View File

@@ -45,6 +45,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(UserDomainClaimedType, DomainClaimedEventMapper).
RegisterFilterEventMapper(UserDomainClaimedSentType, DomainClaimedSentEventMapper).
RegisterFilterEventMapper(UserUserNameChangedType, UsernameChangedEventMapper).
RegisterFilterEventMapper(MetadataSetType, MetadataSetEventMapper).
RegisterFilterEventMapper(MetadataRemovedType, MetadataRemovedEventMapper).
RegisterFilterEventMapper(HumanAddedType, HumanAddedEventMapper).
RegisterFilterEventMapper(HumanRegisteredType, HumanRegisteredEventMapper).
RegisterFilterEventMapper(HumanInitialCodeAddedType, HumanInitialCodeAddedEventMapper).

View File

@@ -0,0 +1,63 @@
package user
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/metadata"
)
const (
MetadataSetType = userEventTypePrefix + metadata.SetEventType
MetadataRemovedType = userEventTypePrefix + metadata.RemovedEventType
)
type MetadataSetEvent struct {
metadata.SetEvent
}
func NewMetadataSetEvent(ctx context.Context, aggregate *eventstore.Aggregate, key string, value []byte) *MetadataSetEvent {
return &MetadataSetEvent{
SetEvent: *metadata.NewSetEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
MetadataSetType),
key,
value),
}
}
func MetadataSetEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := metadata.SetEventMapper(event)
if err != nil {
return nil, err
}
return &MetadataSetEvent{SetEvent: *e.(*metadata.SetEvent)}, nil
}
type MetadataRemovedEvent struct {
metadata.RemovedEvent
}
func NewMetadataRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, key string) *MetadataRemovedEvent {
return &MetadataRemovedEvent{
RemovedEvent: *metadata.NewRemovedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
MetadataRemovedType),
key),
}
}
func MetadataRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e, err := metadata.RemovedEventMapper(event)
if err != nil {
return nil, err
}
return &MetadataRemovedEvent{RemovedEvent: *e.(*metadata.RemovedEvent)}, nil
}

View File

@@ -359,6 +359,11 @@ Errors:
ReadError: Übersetzungsdatei konnte nicht gelesen werden
MergeError: Übersetzungsdatei konnte nicht mit benutzerdefinierten Übersetzungen zusammengeführt werden
NotFound: Übersetzungsdatei existiert nicht
MetaData:
NotFound: Meta Daten konnten nicht gefunden werden
NoData: Meta Daten Liste ist leer
Invalid: Meta Daten sind ungültig
KeyNotExisting: Ein oder mehrere Keys existiert nicht
EventTypes:
user:
added: Benutzer hinzugefügt

View File

@@ -359,6 +359,11 @@ Errors:
ReadError: Error in reading translation file
MergeError: Translation file could not be merged with custom translations
NotFound: Translation file doesn't exist
MetaData:
NotFound: Metadata not found
NoData: Metadata list is empty
Invalid: Metadata is invalid
KeyNotExisting: One or more keys do not exist
EventTypes:
user:
added: User added

View File

@@ -69,6 +69,9 @@ const (
DomainClaimed models.EventType = "user.domain.claimed"
DomainClaimedSent models.EventType = "user.domain.claimed.sent"
UserMetadataSet models.EventType = "user.metadata.set"
UserMetadataRemoved models.EventType = "user.metadata.removed"
)
// the following consts are for user(v2).human