mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 04:07:31 +00:00
chore: move the go code into a subfolder
This commit is contained in:
187
apps/api/internal/command/quota_model.go
Normal file
187
apps/api/internal/command/quota_model.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/repository/quota"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type quotaWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
rollingAggregateID string
|
||||
unit quota.Unit
|
||||
from time.Time
|
||||
resetInterval time.Duration
|
||||
amount uint64
|
||||
limit bool
|
||||
notifications []*quota.SetEventNotification
|
||||
}
|
||||
|
||||
// newQuotaWriteModel aggregateId is filled by reducing unit matching events
|
||||
func newQuotaWriteModel(instanceId, resourceOwner string, unit quota.Unit) *quotaWriteModel {
|
||||
return "aWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
InstanceID: instanceId,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
unit: unit,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *quotaWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
InstanceID(wm.InstanceID).
|
||||
AddQuery().
|
||||
AggregateTypes(quota.AggregateType).
|
||||
EventTypes(
|
||||
quota.AddedEventType,
|
||||
quota.SetEventType,
|
||||
quota.RemovedEventType,
|
||||
).EventData(map[string]interface{}{"unit": wm.unit})
|
||||
|
||||
return query.Builder()
|
||||
}
|
||||
|
||||
func (wm *quotaWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
wm.ChangeDate = event.CreatedAt()
|
||||
switch e := event.(type) {
|
||||
case *quota.SetEvent:
|
||||
wm.rollingAggregateID = e.Aggregate().ID
|
||||
if e.Amount != nil {
|
||||
wm.amount = *e.Amount
|
||||
}
|
||||
if e.From != nil {
|
||||
wm.from = *e.From
|
||||
}
|
||||
if e.Limit != nil {
|
||||
wm.limit = *e.Limit
|
||||
}
|
||||
if e.ResetInterval != nil {
|
||||
wm.resetInterval = *e.ResetInterval
|
||||
}
|
||||
if e.Notifications != nil {
|
||||
wm.notifications = *e.Notifications
|
||||
}
|
||||
case *quota.RemovedEvent:
|
||||
wm.rollingAggregateID = ""
|
||||
}
|
||||
}
|
||||
if err := wm.WriteModel.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
// wm.WriteModel.Reduce() sets the aggregateID to the first event's aggregateID, but we need the last one
|
||||
wm.AggregateID = wm.rollingAggregateID
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
// NewChanges returns all changes that need to be applied to the aggregate.
|
||||
// If createNew is true, all quota properties are set.
|
||||
func (wm *quotaWriteModel) NewChanges(
|
||||
idGenerator id.Generator,
|
||||
createNew bool,
|
||||
amount uint64,
|
||||
from time.Time,
|
||||
resetInterval time.Duration,
|
||||
limit bool,
|
||||
notifications ...*QuotaNotification,
|
||||
) (changes []quota.QuotaChange, err error) {
|
||||
setEventNotifications, err := QuotaNotifications(notifications).newSetEventNotifications(idGenerator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// we sort the input notifications already, so we can return early if they have duplicates
|
||||
err = sortSetEventNotifications(setEventNotifications)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if createNew {
|
||||
return []quota.QuotaChange{
|
||||
quota.ChangeAmount(amount),
|
||||
quota.ChangeFrom(from),
|
||||
quota.ChangeResetInterval(resetInterval),
|
||||
quota.ChangeLimit(limit),
|
||||
quota.ChangeNotifications(setEventNotifications),
|
||||
}, nil
|
||||
}
|
||||
changes = make([]quota.QuotaChange, 0, 5)
|
||||
if wm.amount != amount {
|
||||
changes = append(changes, quota.ChangeAmount(amount))
|
||||
}
|
||||
if wm.from != from {
|
||||
changes = append(changes, quota.ChangeFrom(from))
|
||||
}
|
||||
if wm.resetInterval != resetInterval {
|
||||
changes = append(changes, quota.ChangeResetInterval(resetInterval))
|
||||
}
|
||||
if wm.limit != limit {
|
||||
changes = append(changes, quota.ChangeLimit(limit))
|
||||
}
|
||||
// If the number of notifications differs, we renew the notifications and we can return early
|
||||
if len(setEventNotifications) != len(wm.notifications) {
|
||||
changes = append(changes, quota.ChangeNotifications(setEventNotifications))
|
||||
return changes, nil
|
||||
}
|
||||
// Now we sort the existing notifications too, so comparing the input properties with the existing ones is easier.
|
||||
// We ignore the sorting error for the existing notifications, because this is system state, not user input.
|
||||
// If sorting fails this time, the notifications are listed in the event payload and the projection cleans them up anyway.
|
||||
_ = sortSetEventNotifications(wm.notifications)
|
||||
for i, notification := range setEventNotifications {
|
||||
if notification.CallURL != wm.notifications[i].CallURL ||
|
||||
notification.Percent != wm.notifications[i].Percent ||
|
||||
notification.Repeat != wm.notifications[i].Repeat {
|
||||
changes = append(changes, quota.ChangeNotifications(setEventNotifications))
|
||||
return changes, nil
|
||||
}
|
||||
}
|
||||
return changes, err
|
||||
}
|
||||
|
||||
// newSetEventNotifications returns quota.SetEventNotification elements with generated IDs.
|
||||
func (q QuotaNotifications) newSetEventNotifications(idGenerator id.Generator) (setNotifications []*quota.SetEventNotification, err error) {
|
||||
if q == nil {
|
||||
return make([]*quota.SetEventNotification, 0), nil
|
||||
}
|
||||
notifications := make([]*quota.SetEventNotification, len(q))
|
||||
for idx, notification := range q {
|
||||
notifications[idx] = "a.SetEventNotification{
|
||||
Percent: notification.Percent,
|
||||
Repeat: notification.Repeat,
|
||||
CallURL: notification.CallURL,
|
||||
}
|
||||
notifications[idx].ID, err = idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
// sortSetEventNotifications reports an error if there are duplicate notifications or if a pointer is nil
|
||||
func sortSetEventNotifications(notifications []*quota.SetEventNotification) (err error) {
|
||||
slices.SortFunc(notifications, func(i, j *quota.SetEventNotification) int {
|
||||
if i == nil || j == nil {
|
||||
err = zerrors.ThrowInternal(errors.New("sorting slices of *quota.SetEventNotification with nil pointers is not supported"), "QUOTA-8YXPk", "Errors.Internal")
|
||||
return 0
|
||||
}
|
||||
if i.Percent == j.Percent && i.CallURL == j.CallURL && i.Repeat == j.Repeat {
|
||||
// TODO: translate
|
||||
err = zerrors.ThrowInternal(fmt.Errorf("%+v", i), "QUOTA-Pty2n", "Errors.Quota.Notifications.Duplicate")
|
||||
return 0
|
||||
}
|
||||
if i.Percent < j.Percent ||
|
||||
i.Percent == j.Percent && i.CallURL < j.CallURL ||
|
||||
i.Percent == j.Percent && i.CallURL == j.CallURL && !i.Repeat && j.Repeat {
|
||||
return -1
|
||||
}
|
||||
return +1
|
||||
})
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user