mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:47:32 +00:00
fix: set quotas (#6597)
* feat: set quotas
* fix: start new period on younger anchor
* cleanup e2e config
* fix set notifications
* lint
* test: fix quota projection tests
* fix add quota tests
* make quota fields nullable
* enable amount 0
* fix initial setup
* create a prerelease
* avoid success comments
* fix quota projection primary key
* Revert "fix quota projection primary key"
This reverts commit e72f4d7fa1
.
* simplify write model
* fix aggregate id
* avoid push without changes
* test set quota lifecycle
* test set quota mutations
* fix quota unit test
* fix: quotas
* test quota.set event projection
* use SetQuota in integration tests
* fix: release quotas 3
* reset releaserc
* fix comment
* test notification order doesn't matter
* test notification order doesn't matter
* test with unmarshalled events
* test with unmarshalled events
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/repository/quota"
|
||||
)
|
||||
|
||||
@@ -32,25 +31,25 @@ func (q QuotaUnit) Enum() quota.Unit {
|
||||
}
|
||||
}
|
||||
|
||||
// AddQuota returns and error if the quota already exists.
|
||||
// AddQuota is deprecated. Use SetQuota instead.
|
||||
func (c *Commands) AddQuota(
|
||||
ctx context.Context,
|
||||
q *AddQuota,
|
||||
q *SetQuota,
|
||||
) (*domain.ObjectDetails, error) {
|
||||
instanceId := authz.GetInstance(ctx).InstanceID()
|
||||
|
||||
wm, err := c.getQuotaWriteModel(ctx, instanceId, instanceId, q.Unit.Enum())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if wm.active {
|
||||
if wm.AggregateID != "" {
|
||||
return nil, errors.ThrowAlreadyExists(nil, "COMMAND-WDfFf", "Errors.Quota.AlreadyExists")
|
||||
}
|
||||
aggregateId, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.AddQuotaCommand(quota.NewAggregate(aggregateId, instanceId), q))
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.SetQuotaCommand(quota.NewAggregate(aggregateId, instanceId), wm, true, q))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -65,23 +64,52 @@ func (c *Commands) AddQuota(
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
// SetQuota creates a new quota or updates an existing one.
|
||||
func (c *Commands) SetQuota(
|
||||
ctx context.Context,
|
||||
q *SetQuota,
|
||||
) (*domain.ObjectDetails, error) {
|
||||
instanceId := authz.GetInstance(ctx).InstanceID()
|
||||
wm, err := c.getQuotaWriteModel(ctx, instanceId, instanceId, q.Unit.Enum())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregateId := wm.AggregateID
|
||||
createNewQuota := aggregateId == ""
|
||||
if aggregateId == "" {
|
||||
aggregateId, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.SetQuotaCommand(quota.NewAggregate(aggregateId, instanceId), wm, createNewQuota, q))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cmds) > 0 {
|
||||
events, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(wm, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return writeModelToObjectDetails(&wm.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveQuota(ctx context.Context, unit QuotaUnit) (*domain.ObjectDetails, error) {
|
||||
instanceId := authz.GetInstance(ctx).InstanceID()
|
||||
|
||||
wm, err := c.getQuotaWriteModel(ctx, instanceId, instanceId, unit.Enum())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !wm.active {
|
||||
if wm.AggregateID == "" {
|
||||
return nil, errors.ThrowNotFound(nil, "COMMAND-WDfFf", "Errors.Quota.NotFound")
|
||||
}
|
||||
|
||||
aggregate := quota.NewAggregate(wm.AggregateID, instanceId)
|
||||
|
||||
events := []eventstore.Command{
|
||||
quota.NewRemovedEvent(ctx, &aggregate.Aggregate, unit.Enum()),
|
||||
}
|
||||
events := []eventstore.Command{quota.NewRemovedEvent(ctx, &aggregate.Aggregate, unit.Enum())}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -104,6 +132,16 @@ type QuotaNotification struct {
|
||||
CallURL string
|
||||
}
|
||||
|
||||
// SetQuota configures a quota and activates it if it isn't active already
|
||||
type SetQuota struct {
|
||||
Unit QuotaUnit `json:"unit"`
|
||||
From time.Time `json:"from"`
|
||||
ResetInterval time.Duration `json:"ResetInterval,omitempty"`
|
||||
Amount uint64 `json:"Amount,omitempty"`
|
||||
Limit bool `json:"Limit,omitempty"`
|
||||
Notifications QuotaNotifications `json:"Notifications,omitempty"`
|
||||
}
|
||||
|
||||
type QuotaNotifications []*QuotaNotification
|
||||
|
||||
func (q *QuotaNotification) validate() error {
|
||||
@@ -111,94 +149,51 @@ func (q *QuotaNotification) validate() error {
|
||||
if err != nil {
|
||||
return errors.ThrowInvalidArgument(err, "QUOTA-bZ0Fj", "Errors.Quota.Invalid.CallURL")
|
||||
}
|
||||
|
||||
if !u.IsAbs() || u.Host == "" {
|
||||
return errors.ThrowInvalidArgument(nil, "QUOTA-HAYmN", "Errors.Quota.Invalid.CallURL")
|
||||
}
|
||||
|
||||
if q.Percent < 1 {
|
||||
return errors.ThrowInvalidArgument(nil, "QUOTA-pBfjq", "Errors.Quota.Invalid.Percent")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QuotaNotifications) toAddedEventNotifications(idGenerator id.Generator) ([]*quota.AddedEventNotification, error) {
|
||||
if q == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
notifications := make([]*quota.AddedEventNotification, len(*q))
|
||||
for idx, notification := range *q {
|
||||
|
||||
id, err := idGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifications[idx] = "a.AddedEventNotification{
|
||||
ID: id,
|
||||
Percent: notification.Percent,
|
||||
Repeat: notification.Repeat,
|
||||
CallURL: notification.CallURL,
|
||||
}
|
||||
}
|
||||
|
||||
return notifications, nil
|
||||
}
|
||||
|
||||
type AddQuota struct {
|
||||
Unit QuotaUnit
|
||||
From time.Time
|
||||
ResetInterval time.Duration
|
||||
Amount uint64
|
||||
Limit bool
|
||||
Notifications QuotaNotifications
|
||||
}
|
||||
|
||||
func (q *AddQuota) validate() error {
|
||||
func (q *SetQuota) validate() error {
|
||||
for _, notification := range q.Notifications {
|
||||
if err := notification.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if q.Unit.Enum() == quota.Unimplemented {
|
||||
return errors.ThrowInvalidArgument(nil, "QUOTA-OTeSh", "Errors.Quota.Invalid.Unimplemented")
|
||||
}
|
||||
|
||||
if q.Amount < 1 {
|
||||
if q.Amount < 0 {
|
||||
return errors.ThrowInvalidArgument(nil, "QUOTA-hOKSJ", "Errors.Quota.Invalid.Amount")
|
||||
}
|
||||
|
||||
if q.ResetInterval < time.Minute {
|
||||
return errors.ThrowInvalidArgument(nil, "QUOTA-R5otd", "Errors.Quota.Invalid.ResetInterval")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) AddQuotaCommand(a *quota.Aggregate, q *AddQuota) preparation.Validation {
|
||||
func (c *Commands) SetQuotaCommand(a *quota.Aggregate, wm *quotaWriteModel, createNew bool, q *SetQuota) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
|
||||
if err := q.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) (cmd []eventstore.Command, err error) {
|
||||
|
||||
notifications, err := q.Notifications.toAddedEventNotifications(c.idGenerator)
|
||||
if err != nil {
|
||||
changes, err := wm.NewChanges(c.idGenerator, createNew, q.Amount, q.From, q.ResetInterval, q.Limit, q.Notifications...)
|
||||
if len(changes) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []eventstore.Command{quota.NewAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
return []eventstore.Command{quota.NewSetEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
quota.SetEventType,
|
||||
),
|
||||
q.Unit.Enum(),
|
||||
q.From,
|
||||
q.ResetInterval,
|
||||
q.Amount,
|
||||
q.Limit,
|
||||
notifications,
|
||||
changes...,
|
||||
)}, err
|
||||
},
|
||||
nil
|
||||
|
Reference in New Issue
Block a user