split user projection

This commit is contained in:
adlerhurst 2024-04-23 19:35:20 +02:00
parent 81aef2cf5e
commit a4ed2373ab
6 changed files with 541 additions and 399 deletions

View File

@ -3,8 +3,6 @@ package admin
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object"
org_grpc "github.com/zitadel/zitadel/internal/api/grpc/org"
@ -12,12 +10,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
// cmd_v2 "github.com/zitadel/zitadel/internal/v2/command"
"github.com/zitadel/zitadel/internal/v2/org"
"github.com/zitadel/zitadel/internal/v2/projection"
"github.com/zitadel/zitadel/internal/v2/readmodel"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
org_pb "github.com/zitadel/zitadel/pkg/grpc/org"
)
func (s *Server) IsOrgUnique(ctx context.Context, req *admin_pb.IsOrgUniqueRequest) (*admin_pb.IsOrgUniqueResponse, error) {
@ -67,46 +60,6 @@ func (s *Server) GetOrgByID(ctx context.Context, req *admin_pb.GetOrgByIDRequest
return &admin_pb.GetOrgByIDResponse{Org: org_grpc.OrgViewToPb(org)}, nil
}
func orgToPb(org *readmodel.Org) *org_pb.Org {
res := &org_pb.Org{
Id: org.ID,
State: stateToPb(org.State),
Name: org.Name,
PrimaryDomain: org.PrimaryDomain.Domain,
Details: &object_pb.ObjectDetails{
Sequence: uint64(org.Sequence),
CreationDate: timestamppb.New(org.CreationDate),
ChangeDate: timestamppb.New(org.ChangeDate),
ResourceOwner: org.Owner,
},
}
if !org.CreationDate.IsZero() {
res.Details.CreationDate = timestamppb.New(org.CreationDate)
}
if !org.ChangeDate.IsZero() {
res.Details.ChangeDate = timestamppb.New(org.ChangeDate)
}
return res
}
func stateToPb(state *projection.OrgState) org_pb.OrgState {
switch state.State {
case org.ActiveState:
return org_pb.OrgState_ORG_STATE_ACTIVE
case org.InactiveState:
return org_pb.OrgState_ORG_STATE_INACTIVE
case org.RemovedState:
return org_pb.OrgState_ORG_STATE_REMOVED
case org.UndefinedState:
fallthrough
default:
return org_pb.OrgState_ORG_STATE_UNSPECIFIED
}
}
func (s *Server) ListOrgs(ctx context.Context, req *admin_pb.ListOrgsRequest) (*admin_pb.ListOrgsResponse, error) {
queries, err := listOrgRequestToModel(req)
if err != nil {

View File

@ -1,8 +1,6 @@
package projection
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/user"
@ -124,351 +122,3 @@ func (u *User) Filter() []*eventstore.Filter {
),
}
}
type Machine struct {
projection
id string
Name string
Description string
AccessTokenType domain.OIDCTokenType
// TODO: separate projection?
Secret *string
}
var _ eventstore.Reducer = (*Machine)(nil)
func NewMachineProjection(id string) *Machine {
return &Machine{
id: id,
}
}
// Reduce implements eventstore.Reducer.
func (m *Machine) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) error {
for _, event := range events {
if !m.projection.shouldReduce(event) {
continue
}
switch event.Type {
case "user.machine.added":
e, err := user.MachineAddedEventFromStorage(event)
if err != nil {
return err
}
m.Name = e.Payload.Name
m.Description = e.Payload.Description
m.AccessTokenType = e.Payload.AccessTokenType
case "user.machine.changed":
e, err := user.MachineChangedEventFromStorage(event)
if err != nil {
return err
}
if e.Payload.Name != nil {
m.Name = *e.Payload.Name
}
if e.Payload.Description != nil {
m.Description = *e.Payload.Description
}
if e.Payload.AccessTokenType != nil {
m.AccessTokenType = *e.Payload.AccessTokenType
}
case "user.machine.secret.set":
e, err := user.MachineSecretSetEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
case "user.machine.secret.updated":
e, err := user.MachineSecretHashUpdatedEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
case "user.machine.secret.removed":
e, err := user.MachineSecretHashUpdatedEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
default:
continue
}
m.projection.reduce(event)
}
return nil
}
func (m *Machine) Filter() []*eventstore.Filter {
return []*eventstore.Filter{
eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.GlobalPositionGreater(&m.position),
),
eventstore.AppendAggregateFilter(
user.AggregateType,
eventstore.AggregateID(m.id),
eventstore.AppendEvent(
eventstore.EventTypes(
"user.machine.added",
"user.machine.changed",
"user.machine.secret.set",
"user.machine.secret.updated",
"user.machine.secret.removed",
),
),
),
),
}
}
type Human struct {
projection
id string
FirstName string
LastName string
NickName string
DisplayName string
AvatarKey *string
PreferredLanguage language.Tag
Gender domain.Gender
Email Email
Phone *Phone
PasswordChangeRequired bool
}
type Phone struct {
Number domain.PhoneNumber
IsVerified bool
}
type Email struct {
Address domain.EmailAddress
IsVerified bool
}
var _ eventstore.Reducer = (*Human)(nil)
func NewHumanProjection(id string) *Human {
return &Human{
id: id,
}
}
// Reduce implements eventstore.Reducer.
func (h *Human) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) error {
for _, event := range events {
if !h.projection.shouldReduce(event) {
continue
}
switch event.Type {
case "user.human.added", "user.added":
e, err := user.HumanAddedEventFromStorage(event)
if err != nil {
return err
}
h.FirstName = e.Payload.FirstName
h.LastName = e.Payload.LastName
h.NickName = e.Payload.NickName
h.DisplayName = e.Payload.DisplayName
h.PreferredLanguage = e.Payload.PreferredLanguage
h.Gender = e.Payload.Gender
h.Email.Address = e.Payload.EmailAddress
if e.Payload.PhoneNumber != "" {
h.Phone = &Phone{
Number: e.Payload.PhoneNumber,
}
}
h.PasswordChangeRequired = e.Payload.PasswordChangeRequired
case "user.human.selfregistered":
e, err := user.HumanRegisteredEventFromStorage(event)
if err != nil {
return err
}
h.FirstName = e.Payload.FirstName
h.LastName = e.Payload.LastName
h.NickName = e.Payload.NickName
h.DisplayName = e.Payload.DisplayName
h.PreferredLanguage = e.Payload.PreferredLanguage
h.Gender = e.Payload.Gender
h.Email.Address = e.Payload.EmailAddress
if e.Payload.PhoneNumber != "" {
h.Phone = &Phone{
Number: e.Payload.PhoneNumber,
}
}
h.PasswordChangeRequired = e.Payload.PasswordChangeRequired
case "user.human.profile.changed":
e, err := user.HumanProfileChangedEventFromStorage(event)
if err != nil {
return err
}
if e.Payload.FirstName != "" {
h.FirstName = e.Payload.FirstName
}
if e.Payload.LastName != "" {
h.LastName = e.Payload.LastName
}
if e.Payload.NickName != nil {
h.NickName = *e.Payload.NickName
}
if e.Payload.DisplayName != nil {
h.DisplayName = *e.Payload.DisplayName
}
if e.Payload.PreferredLanguage != nil {
h.PreferredLanguage = *e.Payload.PreferredLanguage
}
if e.Payload.Gender != nil {
h.Gender = *e.Payload.Gender
}
case "user.human.phone.changed":
h.Phone = new(Phone)
e, err := user.HumanPhoneChangedEventFromStorage(event)
if err != nil {
return err
}
h.Phone.Number = e.Payload.PhoneNumber
case "user.human.phone.removed":
h.Phone = nil
case "user.human.phone.verified":
h.Phone.IsVerified = true
case "user.human.email.changed":
h.Email.IsVerified = false
e, err := user.HumanEmailChangedEventFromStorage(event)
if err != nil {
return err
}
h.Email.Address = e.Payload.Address
case "user.human.email.verified":
h.Email.IsVerified = true
case "user.human.avatar.added":
e, err := user.HumanAvatarAddedEventFromStorage(event)
if err != nil {
return err
}
h.AvatarKey = &e.Payload.StoreKey
case "user.human.avatar.removed":
h.AvatarKey = nil
case "user.human.password.changed":
e, err := user.HumanPasswordChangedEventFromStorage(event)
if err != nil {
return err
}
h.PasswordChangeRequired = e.Payload.ChangeRequired
default:
continue
}
h.projection.reduce(event)
}
return nil
}
func (h *Human) Filter() []*eventstore.Filter {
return []*eventstore.Filter{
eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.GlobalPositionGreater(&h.position),
),
eventstore.AppendAggregateFilter(
user.AggregateType,
eventstore.AggregateID(h.id),
eventstore.AppendEvent(
eventstore.EventTypes(
"user.human.added",
"user.added",
"user.human.selfregistered",
"user.human.profile.changed",
"user.human.phone.changed",
"user.human.phone.removed",
"user.human.phone.verified",
"user.human.email.changed",
"user.human.email.verified",
"user.human.avatar.added",
"user.human.avatar.removed",
"user.human.password.changed",
),
),
),
),
}
}
// import (
// "github.com/zitadel/zitadel/internal/v2/eventstore"
// "github.com/zitadel/zitadel/internal/v2/org"
// )
// type OrgState struct {
// projection
// id string
// org.State
// }
// func NewStateProjection(id string) *OrgState {
// // TODO: check buffer for id and return from buffer if exists
// return &OrgState{
// id: id,
// }
// }
// func (p *OrgState) Filter() []*eventstore.Filter {
// return []*eventstore.Filter{
// eventstore.NewFilter(
// eventstore.FilterPagination(
// eventstore.Descending(),
// eventstore.GlobalPositionGreater(&p.position),
// ),
// eventstore.AppendAggregateFilter(
// org.AggregateType,
// eventstore.AggregateID(p.id),
// eventstore.AppendEvent(
// eventstore.EventType("org.added"),
// ),
// eventstore.AppendEvent(
// eventstore.EventType("org.deactivated"),
// ),
// eventstore.AppendEvent(
// eventstore.EventType("org.reactivated"),
// ),
// eventstore.AppendEvent(
// eventstore.EventType("org.removed"),
// ),
// ),
// ),
// }
// }
// func (p *OrgState) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) error {
// for _, event := range events {
// if !p.shouldReduce(event) {
// continue
// }
// switch {
// case org.Added.IsType(event.Type):
// p.State = org.ActiveState
// case org.Deactivated.IsType(event.Type):
// p.State = org.InactiveState
// case org.Reactivated.IsType(event.Type):
// p.State = org.ActiveState
// case org.Removed.IsType(event.Type):
// p.State = org.RemovedState
// default:
// continue
// }
// p.position = event.Position
// }
// // TODO: if more than x events store state
// return nil
// }

View File

@ -0,0 +1,214 @@
package projection
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/user"
)
type Human struct {
projection
id string
FirstName string
LastName string
NickName string
DisplayName string
AvatarKey *string
PreferredLanguage language.Tag
Gender domain.Gender
Email Email
Phone *Phone
PasswordChangeRequired bool
}
type Phone struct {
Number domain.PhoneNumber
IsVerified bool
}
type Email struct {
Address domain.EmailAddress
IsVerified bool
}
var _ eventstore.Reducer = (*Human)(nil)
func NewHumanProjection(id string) *Human {
return &Human{
id: id,
}
}
// Reduce implements eventstore.Reducer.
func (h *Human) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) (err error) {
for _, event := range events {
if !h.projection.shouldReduce(event) {
continue
}
switch event.Type {
case "user.human.added", "user.added":
err = h.reduceAdded(event)
case "user.human.selfregistered":
err = h.reduceRegistered(event)
case "user.human.profile.changed":
err = h.reduceProfileChanged(event)
case "user.human.phone.changed":
err = h.reducePhoneChanged(event)
case "user.human.phone.removed":
h.Phone = nil
case "user.human.phone.verified":
h.Phone.IsVerified = true
case "user.human.email.changed":
err = h.reduceEmailChanged(event)
case "user.human.email.verified":
h.Email.IsVerified = true
case "user.human.avatar.added":
e, err := user.HumanAvatarAddedEventFromStorage(event)
if err != nil {
return err
}
h.AvatarKey = &e.Payload.StoreKey
case "user.human.avatar.removed":
h.AvatarKey = nil
case "user.human.password.changed":
e, err := user.HumanPasswordChangedEventFromStorage(event)
if err != nil {
return err
}
h.PasswordChangeRequired = e.Payload.ChangeRequired
default:
continue
}
if err != nil {
return err
}
h.projection.reduce(event)
}
return nil
}
func (h *Human) Filter() []*eventstore.Filter {
return []*eventstore.Filter{
eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.GlobalPositionGreater(&h.position),
),
eventstore.AppendAggregateFilter(
user.AggregateType,
eventstore.AggregateID(h.id),
eventstore.AppendEvent(
eventstore.EventTypes(
"user.human.added",
"user.added",
"user.human.selfregistered",
"user.human.profile.changed",
"user.human.phone.changed",
"user.human.phone.removed",
"user.human.phone.verified",
"user.human.email.changed",
"user.human.email.verified",
"user.human.avatar.added",
"user.human.avatar.removed",
"user.human.password.changed",
),
),
),
),
}
}
func (h *Human) reduceAdded(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanAddedEventFromStorage(event)
if err != nil {
return err
}
h.FirstName = e.Payload.FirstName
h.LastName = e.Payload.LastName
h.NickName = e.Payload.NickName
h.DisplayName = e.Payload.DisplayName
h.PreferredLanguage = e.Payload.PreferredLanguage
h.Gender = e.Payload.Gender
h.Email.Address = e.Payload.EmailAddress
if e.Payload.PhoneNumber != "" {
h.Phone = &Phone{
Number: e.Payload.PhoneNumber,
}
}
h.PasswordChangeRequired = e.Payload.PasswordChangeRequired
return nil
}
func (h *Human) reduceRegistered(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanRegisteredEventFromStorage(event)
if err != nil {
return err
}
h.FirstName = e.Payload.FirstName
h.LastName = e.Payload.LastName
h.NickName = e.Payload.NickName
h.DisplayName = e.Payload.DisplayName
h.PreferredLanguage = e.Payload.PreferredLanguage
h.Gender = e.Payload.Gender
h.Email.Address = e.Payload.EmailAddress
if e.Payload.PhoneNumber != "" {
h.Phone = &Phone{
Number: e.Payload.PhoneNumber,
}
}
h.PasswordChangeRequired = e.Payload.PasswordChangeRequired
return nil
}
func (h *Human) reduceProfileChanged(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanProfileChangedEventFromStorage(event)
if err != nil {
return err
}
if e.Payload.FirstName != "" {
h.FirstName = e.Payload.FirstName
}
if e.Payload.LastName != "" {
h.LastName = e.Payload.LastName
}
if e.Payload.NickName != nil {
h.NickName = *e.Payload.NickName
}
if e.Payload.DisplayName != nil {
h.DisplayName = *e.Payload.DisplayName
}
if e.Payload.PreferredLanguage != nil {
h.PreferredLanguage = *e.Payload.PreferredLanguage
}
if e.Payload.Gender != nil {
h.Gender = *e.Payload.Gender
}
return nil
}
func (h *Human) reducePhoneChanged(event *eventstore.Event[eventstore.StoragePayload]) error {
h.Phone = new(Phone)
e, err := user.HumanPhoneChangedEventFromStorage(event)
if err != nil {
return err
}
h.Phone.Number = e.Payload.PhoneNumber
return nil
}
func (h *Human) reduceEmailChanged(event *eventstore.Event[eventstore.StoragePayload]) error {
h.Email.IsVerified = false
e, err := user.HumanEmailChangedEventFromStorage(event)
if err != nil {
return err
}
h.Email.Address = e.Payload.Address
return nil
}

View File

@ -0,0 +1,107 @@
package projection
import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/user"
)
type Machine struct {
projection
id string
Name string
Description string
AccessTokenType domain.OIDCTokenType
// TODO: separate projection?
Secret *string
}
var _ eventstore.Reducer = (*Machine)(nil)
func NewMachineProjection(id string) *Machine {
return &Machine{
id: id,
}
}
// Reduce implements eventstore.Reducer.
func (m *Machine) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) error {
for _, event := range events {
if !m.projection.shouldReduce(event) {
continue
}
switch event.Type {
case "user.machine.added":
e, err := user.MachineAddedEventFromStorage(event)
if err != nil {
return err
}
m.Name = e.Payload.Name
m.Description = e.Payload.Description
m.AccessTokenType = e.Payload.AccessTokenType
case "user.machine.changed":
e, err := user.MachineChangedEventFromStorage(event)
if err != nil {
return err
}
if e.Payload.Name != nil {
m.Name = *e.Payload.Name
}
if e.Payload.Description != nil {
m.Description = *e.Payload.Description
}
if e.Payload.AccessTokenType != nil {
m.AccessTokenType = *e.Payload.AccessTokenType
}
case "user.machine.secret.set":
e, err := user.MachineSecretSetEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
case "user.machine.secret.updated":
e, err := user.MachineSecretHashUpdatedEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
case "user.machine.secret.removed":
e, err := user.MachineSecretHashUpdatedEventFromStorage(event)
if err != nil {
return err
}
m.Secret = &e.Payload.HashedSecret
default:
continue
}
m.projection.reduce(event)
}
return nil
}
func (m *Machine) Filter() []*eventstore.Filter {
return []*eventstore.Filter{
eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.GlobalPositionGreater(&m.position),
),
eventstore.AppendAggregateFilter(
user.AggregateType,
eventstore.AggregateID(m.id),
eventstore.AppendEvent(
eventstore.EventTypes(
"user.machine.added",
"user.machine.changed",
"user.machine.secret.set",
"user.machine.secret.updated",
"user.machine.secret.removed",
),
),
),
),
}
}

View File

@ -0,0 +1,206 @@
package projection
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/v2/eventstore"
"github.com/zitadel/zitadel/internal/v2/user"
)
type NotifyUser struct {
projection
id string
Username string
// LoginNames database.TextArray[string]
// PreferredLoginName string
FirstName string
LastName string
NickName string
DisplayName string
AvatarKey *string
PreferredLanguage language.Tag
Gender domain.Gender
LastEmail *string
VerifiedEmail *string
LastPhone *string
VerifiedPhone *string
PasswordSet bool
}
func NewNotifyUser(id string) *NotifyUser {
return &NotifyUser{
id: id,
}
}
func (n *NotifyUser) Reduce(events ...*eventstore.Event[eventstore.StoragePayload]) (err error) {
for _, event := range events {
if !n.shouldReduce(event) {
continue
}
switch event.Type {
case "user.human.added", "user.added":
err = n.reduceAdded(event)
case "user.human.selfregistered":
err = n.reduceRegistered(event)
case "user.human.phone.changed":
err = n.reducePhoneChanged(event)
case "user.human.phone.removed":
err = n.reducePhoneRemoved()
case "user.human.phone.verified":
err = n.reducePhoneVerified()
case "user.human.email.changed":
err = n.reduceEmailChanged(event)
case "user.human.email.verified":
err = n.reduceEmailVerified()
case "user.human.avatar.added":
err = n.reduceAvatarAdded(event)
case "user.human.avatar.removed":
err = n.reduceAvatarRemoved()
case "user.human.password.changed":
err = n.reducePasswordChanged()
}
if err != nil {
return err
}
}
return nil
}
func (n *NotifyUser) Filter() []*eventstore.Filter {
return []*eventstore.Filter{
eventstore.NewFilter(
eventstore.FilterPagination(
eventstore.GlobalPositionGreater(&n.position),
),
eventstore.AppendAggregateFilter(
user.AggregateType,
eventstore.AggregateID(n.id),
eventstore.AppendEvent(
eventstore.EventTypes(
"user.human.added",
"user.added",
"user.human.selfregistered",
"user.human.profile.changed",
"user.human.phone.changed",
"user.human.phone.removed",
"user.human.phone.verified",
"user.human.email.changed",
"user.human.email.verified",
"user.human.avatar.added",
"user.human.avatar.removed",
"user.human.password.changed",
),
),
),
),
}
}
func (n *NotifyUser) reduceAdded(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanAddedEventFromStorage(event)
if err != nil {
return err
}
n.Username = e.Payload.Username
n.FirstName = e.Payload.FirstName
n.LastName = e.Payload.LastName
n.NickName = e.Payload.NickName
n.DisplayName = e.Payload.DisplayName
n.PreferredLanguage = e.Payload.PreferredLanguage
n.Gender = e.Payload.Gender
if e.Payload.EmailAddress != "" {
n.LastEmail = (*string)(&e.Payload.EmailAddress)
}
if e.Payload.PhoneNumber != "" {
n.LastPhone = (*string)(&e.Payload.PhoneNumber)
}
n.PasswordSet = crypto.SecretOrEncodedHash(e.Payload.Secret, e.Payload.EncodedHash) != ""
return nil
}
func (n *NotifyUser) reduceRegistered(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanRegisteredEventFromStorage(event)
if err != nil {
return err
}
n.Username = e.Payload.Username
n.FirstName = e.Payload.FirstName
n.LastName = e.Payload.LastName
n.NickName = e.Payload.NickName
n.DisplayName = e.Payload.DisplayName
n.PreferredLanguage = e.Payload.PreferredLanguage
n.Gender = e.Payload.Gender
if e.Payload.EmailAddress != "" {
n.LastEmail = (*string)(&e.Payload.EmailAddress)
}
if e.Payload.PhoneNumber != "" {
n.LastPhone = (*string)(&e.Payload.PhoneNumber)
}
n.PasswordSet = crypto.SecretOrEncodedHash(e.Payload.Secret, e.Payload.EncodedHash) != ""
return nil
}
func (n *NotifyUser) reducePhoneChanged(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanPhoneChangedEventFromStorage(event)
if err != nil {
return err
}
n.LastPhone = (*string)(&e.Payload.PhoneNumber)
return nil
}
func (n *NotifyUser) reducePhoneRemoved() error {
n.LastPhone = nil
n.VerifiedPhone = nil
return nil
}
func (n *NotifyUser) reducePhoneVerified() error {
n.VerifiedPhone = n.LastPhone
return nil
}
func (n *NotifyUser) reduceEmailChanged(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanEmailChangedEventFromStorage(event)
if err != nil {
return err
}
n.LastEmail = (*string)(&e.Payload.Address)
if e.Payload.Address == "" {
n.LastEmail = nil
}
return nil
}
func (n *NotifyUser) reduceEmailVerified() error {
n.VerifiedEmail = n.LastEmail
return nil
}
func (n *NotifyUser) reducePasswordChanged() error {
n.PasswordSet = true
return nil
}
func (n *NotifyUser) reduceAvatarAdded(event *eventstore.Event[eventstore.StoragePayload]) error {
e, err := user.HumanAvatarAddedEventFromStorage(event)
if err != nil {
return err
}
n.AvatarKey = &e.Payload.StoreKey
return nil
}
func (n *NotifyUser) reduceAvatarRemoved() error {
n.AvatarKey = nil
return nil
}

View File

@ -16,6 +16,7 @@ type User struct {
LoginNames *projection.LoginNames
Human *projection.Human
Machine *projection.Machine
Notify *projection.NotifyUser
}
func (u *User) PreferredLoginName() string {
@ -36,12 +37,19 @@ func NewUser(id string) *User {
User: *projection.NewUserProjection(id),
Human: projection.NewHumanProjection(id),
Machine: projection.NewMachineProjection(id),
Notify: projection.NewNotifyUser(id),
}
}
func (u *User) Query(ctx context.Context, querier eventstore.Querier, opts ...QueryOpt) error {
queryOpts := make([]eventstore.QueryOpt, 0, len(opts)+1)
queryOpts = append(queryOpts, eventstore.AppendFilters(u.User.Filter()...), eventstore.AppendFilters(u.Human.Filter()...), eventstore.AppendFilters(u.Machine.Filter()...))
queryOpts = append(queryOpts,
eventstore.AppendFilters(u.User.Filter()...),
eventstore.AppendFilters(u.Human.Filter()...),
eventstore.AppendFilters(u.Machine.Filter()...),
//TODO: adlerhurst we need to merge the queries because we double the events for humans at the moment
// eventstore.AppendFilters(u.Notify.Filter()...),
)
for _, opt := range opts {
queryOpts = opt(queryOpts)
}
@ -95,6 +103,7 @@ eventLoop:
break eventLoop
case "user.machine.added":
u.Human = nil
u.Notify = nil
break eventLoop
}
}
@ -103,7 +112,10 @@ eventLoop:
}
u.reduce(events[len(events)-1])
if u.Type == domain.UserTypeHuman {
err = u.Human.Reduce(events...)
if err = u.Human.Reduce(events...); err != nil {
return err
}
err = u.Notify.Reduce(events...)
} else if u.Type == domain.UserTypeMachine {
err = u.Machine.Reduce(events...)
}