diff --git a/backend/ent/client.go b/backend/ent/client.go index e9b74fcfeff..48a63692235 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -53,6 +53,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/userplatformquota" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" stdsql "database/sql" ) @@ -138,6 +139,8 @@ type Client struct { UserPlatformQuota *UserPlatformQuotaClient // UserSubscription is the client for interacting with the UserSubscription builders. UserSubscription *UserSubscriptionClient + // UserSubscriptionQuotaEvent is the client for interacting with the UserSubscriptionQuotaEvent builders. + UserSubscriptionQuotaEvent *UserSubscriptionQuotaEventClient } // NewClient creates a new client configured with the given options. @@ -187,6 +190,7 @@ func (c *Client) init() { c.UserAttributeValue = NewUserAttributeValueClient(c.config) c.UserPlatformQuota = NewUserPlatformQuotaClient(c.config) c.UserSubscription = NewUserSubscriptionClient(c.config) + c.UserSubscriptionQuotaEvent = NewUserSubscriptionQuotaEventClient(c.config) } type ( @@ -317,6 +321,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { UserAttributeValue: NewUserAttributeValueClient(cfg), UserPlatformQuota: NewUserPlatformQuotaClient(cfg), UserSubscription: NewUserSubscriptionClient(cfg), + UserSubscriptionQuotaEvent: NewUserSubscriptionQuotaEventClient(cfg), }, nil } @@ -374,6 +379,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) UserAttributeValue: NewUserAttributeValueClient(cfg), UserPlatformQuota: NewUserPlatformQuotaClient(cfg), UserSubscription: NewUserSubscriptionClient(cfg), + UserSubscriptionQuotaEvent: NewUserSubscriptionQuotaEventClient(cfg), }, nil } @@ -413,7 +419,7 @@ func (c *Client) Use(hooks ...Hook) { c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, - c.UserPlatformQuota, c.UserSubscription, + c.UserPlatformQuota, c.UserSubscription, c.UserSubscriptionQuotaEvent, } { n.Use(hooks...) } @@ -433,7 +439,7 @@ func (c *Client) Intercept(interceptors ...Interceptor) { c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue, - c.UserPlatformQuota, c.UserSubscription, + c.UserPlatformQuota, c.UserSubscription, c.UserSubscriptionQuotaEvent, } { n.Intercept(interceptors...) } @@ -518,6 +524,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.UserPlatformQuota.mutate(ctx, m) case *UserSubscriptionMutation: return c.UserSubscription.mutate(ctx, m) + case *UserSubscriptionQuotaEventMutation: + return c.UserSubscriptionQuotaEvent.mutate(ctx, m) default: return nil, fmt.Errorf("ent: unknown mutation type %T", m) } @@ -6636,6 +6644,22 @@ func (c *UserSubscriptionClient) QueryUsageLogs(_m *UserSubscription) *UsageLogQ return query } +// QueryQuotaEvents queries the quota_events edge of a UserSubscription. +func (c *UserSubscriptionClient) QueryQuotaEvents(_m *UserSubscription) *UserSubscriptionQuotaEventQuery { + query := (&UserSubscriptionQuotaEventClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(usersubscription.Table, usersubscription.FieldID, id), + sqlgraph.To(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, usersubscription.QuotaEventsTable, usersubscription.QuotaEventsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *UserSubscriptionClient) Hooks() []Hook { hooks := c.hooks.UserSubscription @@ -6663,6 +6687,155 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription } } +// UserSubscriptionQuotaEventClient is a client for the UserSubscriptionQuotaEvent schema. +type UserSubscriptionQuotaEventClient struct { + config +} + +// NewUserSubscriptionQuotaEventClient returns a client for the UserSubscriptionQuotaEvent from the given config. +func NewUserSubscriptionQuotaEventClient(c config) *UserSubscriptionQuotaEventClient { + return &UserSubscriptionQuotaEventClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `usersubscriptionquotaevent.Hooks(f(g(h())))`. +func (c *UserSubscriptionQuotaEventClient) Use(hooks ...Hook) { + c.hooks.UserSubscriptionQuotaEvent = append(c.hooks.UserSubscriptionQuotaEvent, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `usersubscriptionquotaevent.Intercept(f(g(h())))`. +func (c *UserSubscriptionQuotaEventClient) Intercept(interceptors ...Interceptor) { + c.inters.UserSubscriptionQuotaEvent = append(c.inters.UserSubscriptionQuotaEvent, interceptors...) +} + +// Create returns a builder for creating a UserSubscriptionQuotaEvent entity. +func (c *UserSubscriptionQuotaEventClient) Create() *UserSubscriptionQuotaEventCreate { + mutation := newUserSubscriptionQuotaEventMutation(c.config, OpCreate) + return &UserSubscriptionQuotaEventCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of UserSubscriptionQuotaEvent entities. +func (c *UserSubscriptionQuotaEventClient) CreateBulk(builders ...*UserSubscriptionQuotaEventCreate) *UserSubscriptionQuotaEventCreateBulk { + return &UserSubscriptionQuotaEventCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *UserSubscriptionQuotaEventClient) MapCreateBulk(slice any, setFunc func(*UserSubscriptionQuotaEventCreate, int)) *UserSubscriptionQuotaEventCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &UserSubscriptionQuotaEventCreateBulk{err: fmt.Errorf("calling to UserSubscriptionQuotaEventClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*UserSubscriptionQuotaEventCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &UserSubscriptionQuotaEventCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for UserSubscriptionQuotaEvent. +func (c *UserSubscriptionQuotaEventClient) Update() *UserSubscriptionQuotaEventUpdate { + mutation := newUserSubscriptionQuotaEventMutation(c.config, OpUpdate) + return &UserSubscriptionQuotaEventUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *UserSubscriptionQuotaEventClient) UpdateOne(_m *UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventUpdateOne { + mutation := newUserSubscriptionQuotaEventMutation(c.config, OpUpdateOne, withUserSubscriptionQuotaEvent(_m)) + return &UserSubscriptionQuotaEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *UserSubscriptionQuotaEventClient) UpdateOneID(id int64) *UserSubscriptionQuotaEventUpdateOne { + mutation := newUserSubscriptionQuotaEventMutation(c.config, OpUpdateOne, withUserSubscriptionQuotaEventID(id)) + return &UserSubscriptionQuotaEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for UserSubscriptionQuotaEvent. +func (c *UserSubscriptionQuotaEventClient) Delete() *UserSubscriptionQuotaEventDelete { + mutation := newUserSubscriptionQuotaEventMutation(c.config, OpDelete) + return &UserSubscriptionQuotaEventDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *UserSubscriptionQuotaEventClient) DeleteOne(_m *UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *UserSubscriptionQuotaEventClient) DeleteOneID(id int64) *UserSubscriptionQuotaEventDeleteOne { + builder := c.Delete().Where(usersubscriptionquotaevent.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &UserSubscriptionQuotaEventDeleteOne{builder} +} + +// Query returns a query builder for UserSubscriptionQuotaEvent. +func (c *UserSubscriptionQuotaEventClient) Query() *UserSubscriptionQuotaEventQuery { + return &UserSubscriptionQuotaEventQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeUserSubscriptionQuotaEvent}, + inters: c.Interceptors(), + } +} + +// Get returns a UserSubscriptionQuotaEvent entity by its id. +func (c *UserSubscriptionQuotaEventClient) Get(ctx context.Context, id int64) (*UserSubscriptionQuotaEvent, error) { + return c.Query().Where(usersubscriptionquotaevent.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *UserSubscriptionQuotaEventClient) GetX(ctx context.Context, id int64) *UserSubscriptionQuotaEvent { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QuerySubscription queries the subscription edge of a UserSubscriptionQuotaEvent. +func (c *UserSubscriptionQuotaEventClient) QuerySubscription(_m *UserSubscriptionQuotaEvent) *UserSubscriptionQuery { + query := (&UserSubscriptionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.FieldID, id), + sqlgraph.To(usersubscription.Table, usersubscription.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usersubscriptionquotaevent.SubscriptionTable, usersubscriptionquotaevent.SubscriptionColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *UserSubscriptionQuotaEventClient) Hooks() []Hook { + return c.hooks.UserSubscriptionQuotaEvent +} + +// Interceptors returns the client interceptors. +func (c *UserSubscriptionQuotaEventClient) Interceptors() []Interceptor { + return c.inters.UserSubscriptionQuotaEvent +} + +func (c *UserSubscriptionQuotaEventClient) mutate(ctx context.Context, m *UserSubscriptionQuotaEventMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&UserSubscriptionQuotaEventCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&UserSubscriptionQuotaEventUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&UserSubscriptionQuotaEventUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&UserSubscriptionQuotaEventDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown UserSubscriptionQuotaEvent mutation op: %q", m.Op()) + } +} + // hooks and interceptors per client, for fast access. type ( hooks struct { @@ -6674,7 +6847,8 @@ type ( PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, - UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Hook + UserAttributeValue, UserPlatformQuota, UserSubscription, + UserSubscriptionQuotaEvent []ent.Hook } inters struct { APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity, @@ -6685,7 +6859,8 @@ type ( PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition, - UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Interceptor + UserAttributeValue, UserPlatformQuota, UserSubscription, + UserSubscriptionQuotaEvent []ent.Interceptor } ) diff --git a/backend/ent/ent.go b/backend/ent/ent.go index d23f61327ff..c3594cee9d2 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -50,6 +50,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/userplatformquota" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" ) // ent aliases to avoid import conflicts in user's code. @@ -148,6 +149,7 @@ func checkColumn(t, c string) error { userattributevalue.Table: userattributevalue.ValidColumn, userplatformquota.Table: userplatformquota.ValidColumn, usersubscription.Table: usersubscription.ValidColumn, + usersubscriptionquotaevent.Table: usersubscriptionquotaevent.ValidColumn, }) }) return columnCheck(t, c) diff --git a/backend/ent/group.go b/backend/ent/group.go index 4cff5086981..f982dbf053c 100644 --- a/backend/ent/group.go +++ b/backend/ent/group.go @@ -55,6 +55,8 @@ type Group struct { WeeklyLimitUsd *float64 `json:"weekly_limit_usd,omitempty"` // MonthlyLimitUsd holds the value of the "monthly_limit_usd" field. MonthlyLimitUsd *float64 `json:"monthly_limit_usd,omitempty"` + // TotalLimitUsd holds the value of the "total_limit_usd" field. + TotalLimitUsd *float64 `json:"total_limit_usd,omitempty"` // DefaultValidityDays holds the value of the "default_validity_days" field. DefaultValidityDays int `json:"default_validity_days,omitempty"` // 是否允许该分组使用图片生成能力 @@ -231,7 +233,7 @@ func (*Group) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case group.FieldPeakRateEnabled, group.FieldIsExclusive, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, group.FieldVideoRateIndependent, group.FieldClaudeCodeOnly, group.FieldModelRoutingEnabled, group.FieldMcpXMLInject, group.FieldAllowMessagesDispatch, group.FieldRequireOauthOnly, group.FieldRequirePrivacySet: values[i] = new(sql.NullBool) - case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p, group.FieldWebSearchPricePerCall: + case group.FieldRateMultiplier, group.FieldPeakRateMultiplier, group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, group.FieldTotalLimitUsd, group.FieldImageRateMultiplier, group.FieldImagePrice1k, group.FieldImagePrice2k, group.FieldImagePrice4k, group.FieldBatchImageDiscountMultiplier, group.FieldBatchImageHoldMultiplier, group.FieldVideoRateMultiplier, group.FieldVideoPrice480p, group.FieldVideoPrice720p, group.FieldVideoPrice1080p, group.FieldWebSearchPricePerCall: values[i] = new(sql.NullFloat64) case group.FieldID, group.FieldDefaultValidityDays, group.FieldFallbackGroupID, group.FieldFallbackGroupIDOnInvalidRequest, group.FieldSortOrder, group.FieldRpmLimit: values[i] = new(sql.NullInt64) @@ -374,6 +376,13 @@ func (_m *Group) assignValues(columns []string, values []any) error { _m.MonthlyLimitUsd = new(float64) *_m.MonthlyLimitUsd = value.Float64 } + case group.FieldTotalLimitUsd: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field total_limit_usd", values[i]) + } else if value.Valid { + _m.TotalLimitUsd = new(float64) + *_m.TotalLimitUsd = value.Float64 + } case group.FieldDefaultValidityDays: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field default_validity_days", values[i]) @@ -733,6 +742,11 @@ func (_m *Group) String() string { builder.WriteString(fmt.Sprintf("%v", *v)) } builder.WriteString(", ") + if v := _m.TotalLimitUsd; v != nil { + builder.WriteString("total_limit_usd=") + builder.WriteString(fmt.Sprintf("%v", *v)) + } + builder.WriteString(", ") builder.WriteString("default_validity_days=") builder.WriteString(fmt.Sprintf("%v", _m.DefaultValidityDays)) builder.WriteString(", ") diff --git a/backend/ent/group/group.go b/backend/ent/group/group.go index cdf0bb9cd3d..86c433e5766 100644 --- a/backend/ent/group/group.go +++ b/backend/ent/group/group.go @@ -52,6 +52,8 @@ const ( FieldWeeklyLimitUsd = "weekly_limit_usd" // FieldMonthlyLimitUsd holds the string denoting the monthly_limit_usd field in the database. FieldMonthlyLimitUsd = "monthly_limit_usd" + // FieldTotalLimitUsd holds the string denoting the total_limit_usd field in the database. + FieldTotalLimitUsd = "total_limit_usd" // FieldDefaultValidityDays holds the string denoting the default_validity_days field in the database. FieldDefaultValidityDays = "default_validity_days" // FieldAllowImageGeneration holds the string denoting the allow_image_generation field in the database. @@ -211,6 +213,7 @@ var Columns = []string{ FieldDailyLimitUsd, FieldWeeklyLimitUsd, FieldMonthlyLimitUsd, + FieldTotalLimitUsd, FieldDefaultValidityDays, FieldAllowImageGeneration, FieldAllowBatchImageGeneration, @@ -461,6 +464,11 @@ func ByMonthlyLimitUsd(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldMonthlyLimitUsd, opts...).ToFunc() } +// ByTotalLimitUsd orders the results by the total_limit_usd field. +func ByTotalLimitUsd(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldTotalLimitUsd, opts...).ToFunc() +} + // ByDefaultValidityDays orders the results by the default_validity_days field. func ByDefaultValidityDays(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldDefaultValidityDays, opts...).ToFunc() diff --git a/backend/ent/group/where.go b/backend/ent/group/where.go index 9320209433b..efa89d39de0 100644 --- a/backend/ent/group/where.go +++ b/backend/ent/group/where.go @@ -145,6 +145,11 @@ func MonthlyLimitUsd(v float64) predicate.Group { return predicate.Group(sql.FieldEQ(FieldMonthlyLimitUsd, v)) } +// TotalLimitUsd applies equality check predicate on the "total_limit_usd" field. It's identical to TotalLimitUsdEQ. +func TotalLimitUsd(v float64) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldTotalLimitUsd, v)) +} + // DefaultValidityDays applies equality check predicate on the "default_validity_days" field. It's identical to DefaultValidityDaysEQ. func DefaultValidityDays(v int) predicate.Group { return predicate.Group(sql.FieldEQ(FieldDefaultValidityDays, v)) @@ -1205,6 +1210,56 @@ func MonthlyLimitUsdNotNil() predicate.Group { return predicate.Group(sql.FieldNotNull(FieldMonthlyLimitUsd)) } +// TotalLimitUsdEQ applies the EQ predicate on the "total_limit_usd" field. +func TotalLimitUsdEQ(v float64) predicate.Group { + return predicate.Group(sql.FieldEQ(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdNEQ applies the NEQ predicate on the "total_limit_usd" field. +func TotalLimitUsdNEQ(v float64) predicate.Group { + return predicate.Group(sql.FieldNEQ(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdIn applies the In predicate on the "total_limit_usd" field. +func TotalLimitUsdIn(vs ...float64) predicate.Group { + return predicate.Group(sql.FieldIn(FieldTotalLimitUsd, vs...)) +} + +// TotalLimitUsdNotIn applies the NotIn predicate on the "total_limit_usd" field. +func TotalLimitUsdNotIn(vs ...float64) predicate.Group { + return predicate.Group(sql.FieldNotIn(FieldTotalLimitUsd, vs...)) +} + +// TotalLimitUsdGT applies the GT predicate on the "total_limit_usd" field. +func TotalLimitUsdGT(v float64) predicate.Group { + return predicate.Group(sql.FieldGT(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdGTE applies the GTE predicate on the "total_limit_usd" field. +func TotalLimitUsdGTE(v float64) predicate.Group { + return predicate.Group(sql.FieldGTE(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdLT applies the LT predicate on the "total_limit_usd" field. +func TotalLimitUsdLT(v float64) predicate.Group { + return predicate.Group(sql.FieldLT(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdLTE applies the LTE predicate on the "total_limit_usd" field. +func TotalLimitUsdLTE(v float64) predicate.Group { + return predicate.Group(sql.FieldLTE(FieldTotalLimitUsd, v)) +} + +// TotalLimitUsdIsNil applies the IsNil predicate on the "total_limit_usd" field. +func TotalLimitUsdIsNil() predicate.Group { + return predicate.Group(sql.FieldIsNull(FieldTotalLimitUsd)) +} + +// TotalLimitUsdNotNil applies the NotNil predicate on the "total_limit_usd" field. +func TotalLimitUsdNotNil() predicate.Group { + return predicate.Group(sql.FieldNotNull(FieldTotalLimitUsd)) +} + // DefaultValidityDaysEQ applies the EQ predicate on the "default_validity_days" field. func DefaultValidityDaysEQ(v int) predicate.Group { return predicate.Group(sql.FieldEQ(FieldDefaultValidityDays, v)) diff --git a/backend/ent/group_create.go b/backend/ent/group_create.go index bfd104bcee9..952cb184511 100644 --- a/backend/ent/group_create.go +++ b/backend/ent/group_create.go @@ -273,6 +273,20 @@ func (_c *GroupCreate) SetNillableMonthlyLimitUsd(v *float64) *GroupCreate { return _c } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (_c *GroupCreate) SetTotalLimitUsd(v float64) *GroupCreate { + _c.mutation.SetTotalLimitUsd(v) + return _c +} + +// SetNillableTotalLimitUsd sets the "total_limit_usd" field if the given value is not nil. +func (_c *GroupCreate) SetNillableTotalLimitUsd(v *float64) *GroupCreate { + if v != nil { + _c.SetTotalLimitUsd(*v) + } + return _c +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (_c *GroupCreate) SetDefaultValidityDays(v int) *GroupCreate { _c.mutation.SetDefaultValidityDays(v) @@ -1234,6 +1248,10 @@ func (_c *GroupCreate) createSpec() (*Group, *sqlgraph.CreateSpec) { _spec.SetField(group.FieldMonthlyLimitUsd, field.TypeFloat64, value) _node.MonthlyLimitUsd = &value } + if value, ok := _c.mutation.TotalLimitUsd(); ok { + _spec.SetField(group.FieldTotalLimitUsd, field.TypeFloat64, value) + _node.TotalLimitUsd = &value + } if value, ok := _c.mutation.DefaultValidityDays(); ok { _spec.SetField(group.FieldDefaultValidityDays, field.TypeInt, value) _node.DefaultValidityDays = value @@ -1774,6 +1792,30 @@ func (u *GroupUpsert) ClearMonthlyLimitUsd() *GroupUpsert { return u } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (u *GroupUpsert) SetTotalLimitUsd(v float64) *GroupUpsert { + u.Set(group.FieldTotalLimitUsd, v) + return u +} + +// UpdateTotalLimitUsd sets the "total_limit_usd" field to the value that was provided on create. +func (u *GroupUpsert) UpdateTotalLimitUsd() *GroupUpsert { + u.SetExcluded(group.FieldTotalLimitUsd) + return u +} + +// AddTotalLimitUsd adds v to the "total_limit_usd" field. +func (u *GroupUpsert) AddTotalLimitUsd(v float64) *GroupUpsert { + u.Add(group.FieldTotalLimitUsd, v) + return u +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (u *GroupUpsert) ClearTotalLimitUsd() *GroupUpsert { + u.SetNull(group.FieldTotalLimitUsd) + return u +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (u *GroupUpsert) SetDefaultValidityDays(v int) *GroupUpsert { u.Set(group.FieldDefaultValidityDays, v) @@ -2668,6 +2710,34 @@ func (u *GroupUpsertOne) ClearMonthlyLimitUsd() *GroupUpsertOne { }) } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (u *GroupUpsertOne) SetTotalLimitUsd(v float64) *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.SetTotalLimitUsd(v) + }) +} + +// AddTotalLimitUsd adds v to the "total_limit_usd" field. +func (u *GroupUpsertOne) AddTotalLimitUsd(v float64) *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.AddTotalLimitUsd(v) + }) +} + +// UpdateTotalLimitUsd sets the "total_limit_usd" field to the value that was provided on create. +func (u *GroupUpsertOne) UpdateTotalLimitUsd() *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.UpdateTotalLimitUsd() + }) +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (u *GroupUpsertOne) ClearTotalLimitUsd() *GroupUpsertOne { + return u.Update(func(s *GroupUpsert) { + s.ClearTotalLimitUsd() + }) +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (u *GroupUpsertOne) SetDefaultValidityDays(v int) *GroupUpsertOne { return u.Update(func(s *GroupUpsert) { @@ -3820,6 +3890,34 @@ func (u *GroupUpsertBulk) ClearMonthlyLimitUsd() *GroupUpsertBulk { }) } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (u *GroupUpsertBulk) SetTotalLimitUsd(v float64) *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.SetTotalLimitUsd(v) + }) +} + +// AddTotalLimitUsd adds v to the "total_limit_usd" field. +func (u *GroupUpsertBulk) AddTotalLimitUsd(v float64) *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.AddTotalLimitUsd(v) + }) +} + +// UpdateTotalLimitUsd sets the "total_limit_usd" field to the value that was provided on create. +func (u *GroupUpsertBulk) UpdateTotalLimitUsd() *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.UpdateTotalLimitUsd() + }) +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (u *GroupUpsertBulk) ClearTotalLimitUsd() *GroupUpsertBulk { + return u.Update(func(s *GroupUpsert) { + s.ClearTotalLimitUsd() + }) +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (u *GroupUpsertBulk) SetDefaultValidityDays(v int) *GroupUpsertBulk { return u.Update(func(s *GroupUpsert) { diff --git a/backend/ent/group_update.go b/backend/ent/group_update.go index 52383d76686..196014d6280 100644 --- a/backend/ent/group_update.go +++ b/backend/ent/group_update.go @@ -317,6 +317,33 @@ func (_u *GroupUpdate) ClearMonthlyLimitUsd() *GroupUpdate { return _u } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (_u *GroupUpdate) SetTotalLimitUsd(v float64) *GroupUpdate { + _u.mutation.ResetTotalLimitUsd() + _u.mutation.SetTotalLimitUsd(v) + return _u +} + +// SetNillableTotalLimitUsd sets the "total_limit_usd" field if the given value is not nil. +func (_u *GroupUpdate) SetNillableTotalLimitUsd(v *float64) *GroupUpdate { + if v != nil { + _u.SetTotalLimitUsd(*v) + } + return _u +} + +// AddTotalLimitUsd adds value to the "total_limit_usd" field. +func (_u *GroupUpdate) AddTotalLimitUsd(v float64) *GroupUpdate { + _u.mutation.AddTotalLimitUsd(v) + return _u +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (_u *GroupUpdate) ClearTotalLimitUsd() *GroupUpdate { + _u.mutation.ClearTotalLimitUsd() + return _u +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (_u *GroupUpdate) SetDefaultValidityDays(v int) *GroupUpdate { _u.mutation.ResetDefaultValidityDays() @@ -1340,6 +1367,15 @@ func (_u *GroupUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.MonthlyLimitUsdCleared() { _spec.ClearField(group.FieldMonthlyLimitUsd, field.TypeFloat64) } + if value, ok := _u.mutation.TotalLimitUsd(); ok { + _spec.SetField(group.FieldTotalLimitUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedTotalLimitUsd(); ok { + _spec.AddField(group.FieldTotalLimitUsd, field.TypeFloat64, value) + } + if _u.mutation.TotalLimitUsdCleared() { + _spec.ClearField(group.FieldTotalLimitUsd, field.TypeFloat64) + } if value, ok := _u.mutation.DefaultValidityDays(); ok { _spec.SetField(group.FieldDefaultValidityDays, field.TypeInt, value) } @@ -2122,6 +2158,33 @@ func (_u *GroupUpdateOne) ClearMonthlyLimitUsd() *GroupUpdateOne { return _u } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (_u *GroupUpdateOne) SetTotalLimitUsd(v float64) *GroupUpdateOne { + _u.mutation.ResetTotalLimitUsd() + _u.mutation.SetTotalLimitUsd(v) + return _u +} + +// SetNillableTotalLimitUsd sets the "total_limit_usd" field if the given value is not nil. +func (_u *GroupUpdateOne) SetNillableTotalLimitUsd(v *float64) *GroupUpdateOne { + if v != nil { + _u.SetTotalLimitUsd(*v) + } + return _u +} + +// AddTotalLimitUsd adds value to the "total_limit_usd" field. +func (_u *GroupUpdateOne) AddTotalLimitUsd(v float64) *GroupUpdateOne { + _u.mutation.AddTotalLimitUsd(v) + return _u +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (_u *GroupUpdateOne) ClearTotalLimitUsd() *GroupUpdateOne { + _u.mutation.ClearTotalLimitUsd() + return _u +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (_u *GroupUpdateOne) SetDefaultValidityDays(v int) *GroupUpdateOne { _u.mutation.ResetDefaultValidityDays() @@ -3175,6 +3238,15 @@ func (_u *GroupUpdateOne) sqlSave(ctx context.Context) (_node *Group, err error) if _u.mutation.MonthlyLimitUsdCleared() { _spec.ClearField(group.FieldMonthlyLimitUsd, field.TypeFloat64) } + if value, ok := _u.mutation.TotalLimitUsd(); ok { + _spec.SetField(group.FieldTotalLimitUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedTotalLimitUsd(); ok { + _spec.AddField(group.FieldTotalLimitUsd, field.TypeFloat64, value) + } + if _u.mutation.TotalLimitUsdCleared() { + _spec.ClearField(group.FieldTotalLimitUsd, field.TypeFloat64) + } if value, ok := _u.mutation.DefaultValidityDays(); ok { _spec.SetField(group.FieldDefaultValidityDays, field.TypeInt, value) } diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index 181f2f99dbb..a5887d12b5f 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -465,6 +465,18 @@ func (f UserSubscriptionFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.V return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserSubscriptionMutation", m) } +// The UserSubscriptionQuotaEventFunc type is an adapter to allow the use of ordinary +// function as UserSubscriptionQuotaEvent mutator. +type UserSubscriptionQuotaEventFunc func(context.Context, *ent.UserSubscriptionQuotaEventMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f UserSubscriptionQuotaEventFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.UserSubscriptionQuotaEventMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.UserSubscriptionQuotaEventMutation", m) +} + // Condition is a hook condition function. type Condition func(context.Context, ent.Mutation) bool diff --git a/backend/ent/intercept/intercept.go b/backend/ent/intercept/intercept.go index 7aeb07692dd..81607be9b4e 100644 --- a/backend/ent/intercept/intercept.go +++ b/backend/ent/intercept/intercept.go @@ -47,6 +47,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/userplatformquota" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" ) // The Query interface represents an operation that queries a graph. @@ -1131,6 +1132,33 @@ func (f TraverseUserSubscription) Traverse(ctx context.Context, q ent.Query) err return fmt.Errorf("unexpected query type %T. expect *ent.UserSubscriptionQuery", q) } +// The UserSubscriptionQuotaEventFunc type is an adapter to allow the use of ordinary function as a Querier. +type UserSubscriptionQuotaEventFunc func(context.Context, *ent.UserSubscriptionQuotaEventQuery) (ent.Value, error) + +// Query calls f(ctx, q). +func (f UserSubscriptionQuotaEventFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) { + if q, ok := q.(*ent.UserSubscriptionQuotaEventQuery); ok { + return f(ctx, q) + } + return nil, fmt.Errorf("unexpected query type %T. expect *ent.UserSubscriptionQuotaEventQuery", q) +} + +// The TraverseUserSubscriptionQuotaEvent type is an adapter to allow the use of ordinary function as Traverser. +type TraverseUserSubscriptionQuotaEvent func(context.Context, *ent.UserSubscriptionQuotaEventQuery) error + +// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline. +func (f TraverseUserSubscriptionQuotaEvent) Intercept(next ent.Querier) ent.Querier { + return next +} + +// Traverse calls f(ctx, q). +func (f TraverseUserSubscriptionQuotaEvent) Traverse(ctx context.Context, q ent.Query) error { + if q, ok := q.(*ent.UserSubscriptionQuotaEventQuery); ok { + return f(ctx, q) + } + return fmt.Errorf("unexpected query type %T. expect *ent.UserSubscriptionQuotaEventQuery", q) +} + // NewQuery returns the generic Query interface for the given typed query. func NewQuery(q ent.Query) (Query, error) { switch q := q.(type) { @@ -1210,6 +1238,8 @@ func NewQuery(q ent.Query) (Query, error) { return &query[*ent.UserPlatformQuotaQuery, predicate.UserPlatformQuota, userplatformquota.OrderOption]{typ: ent.TypeUserPlatformQuota, tq: q}, nil case *ent.UserSubscriptionQuery: return &query[*ent.UserSubscriptionQuery, predicate.UserSubscription, usersubscription.OrderOption]{typ: ent.TypeUserSubscription, tq: q}, nil + case *ent.UserSubscriptionQuotaEventQuery: + return &query[*ent.UserSubscriptionQuotaEventQuery, predicate.UserSubscriptionQuotaEvent, usersubscriptionquotaevent.OrderOption]{typ: ent.TypeUserSubscriptionQuotaEvent, tq: q}, nil default: return nil, fmt.Errorf("unknown query type %T", q) } diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 9d979e4c213..be2ea80d176 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -851,6 +851,7 @@ var ( {Name: "daily_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "weekly_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "monthly_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, + {Name: "total_limit_usd", Type: field.TypeFloat64, Nullable: true, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "default_validity_days", Type: field.TypeInt, Default: 30}, {Name: "allow_image_generation", Type: field.TypeBool, Default: false}, {Name: "allow_batch_image_generation", Type: field.TypeBool, Default: false}, @@ -919,7 +920,7 @@ var ( { Name: "group_sort_order", Unique: false, - Columns: []*schema.Column{GroupsColumns[42]}, + Columns: []*schema.Column{GroupsColumns[43]}, }, { Name: "idx_groups_duplicate_operation_id_active", @@ -1998,6 +1999,55 @@ var ( }, }, } + // UserSubscriptionQuotaEventsColumns holds the columns for the "user_subscription_quota_events" table. + UserSubscriptionQuotaEventsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt64, Increment: true}, + {Name: "quota_total_usd", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "decimal(20,10)"}}, + {Name: "quota_used_usd", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,10)"}}, + {Name: "starts_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "expires_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "source_kind", Type: field.TypeString, Size: 32}, + {Name: "source_ref", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, + {Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}}, + {Name: "user_subscription_id", Type: field.TypeInt64}, + } + // UserSubscriptionQuotaEventsTable holds the schema information for the "user_subscription_quota_events" table. + UserSubscriptionQuotaEventsTable = &schema.Table{ + Name: "user_subscription_quota_events", + Columns: UserSubscriptionQuotaEventsColumns, + PrimaryKey: []*schema.Column{UserSubscriptionQuotaEventsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_subscription_quota_events_user_subscriptions_quota_events", + Columns: []*schema.Column{UserSubscriptionQuotaEventsColumns[9]}, + RefColumns: []*schema.Column{UserSubscriptionsColumns[0]}, + OnDelete: schema.NoAction, + }, + }, + Indexes: []*schema.Index{ + { + Name: "usersubscriptionquotaevent_user_subscription_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionQuotaEventsColumns[9]}, + }, + { + Name: "usersubscriptionquotaevent_expires_at", + Unique: false, + Columns: []*schema.Column{UserSubscriptionQuotaEventsColumns[4]}, + }, + { + Name: "usersubscriptionquotaevent_user_subscription_id_expires_at", + Unique: false, + Columns: []*schema.Column{UserSubscriptionQuotaEventsColumns[9], UserSubscriptionQuotaEventsColumns[4]}, + }, + { + Name: "usersubscriptionquotaevent_user_subscription_id_expires_at_id", + Unique: false, + Columns: []*schema.Column{UserSubscriptionQuotaEventsColumns[9], UserSubscriptionQuotaEventsColumns[4], UserSubscriptionQuotaEventsColumns[0]}, + }, + }, + } // Tables holds all the tables in the schema. Tables = []*schema.Table{ APIKeysTable, @@ -2038,6 +2088,7 @@ var ( UserAttributeValuesTable, UserPlatformQuotasTable, UserSubscriptionsTable, + UserSubscriptionQuotaEventsTable, } ) @@ -2191,4 +2242,8 @@ func init() { UserSubscriptionsTable.Annotation = &entsql.Annotation{ Table: "user_subscriptions", } + UserSubscriptionQuotaEventsTable.ForeignKeys[0].RefTable = UserSubscriptionsTable + UserSubscriptionQuotaEventsTable.Annotation = &entsql.Annotation{ + Table: "user_subscription_quota_events", + } } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 1e69df30fc6..a9632da97bf 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -4,7 +4,7 @@ package ent import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "sync" @@ -51,6 +51,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/userplatformquota" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" "github.com/Wei-Shaw/sub2api/internal/domain" ) @@ -101,6 +102,7 @@ const ( TypeUserAttributeValue = "UserAttributeValue" TypeUserPlatformQuota = "UserPlatformQuota" TypeUserSubscription = "UserSubscription" + TypeUserSubscriptionQuotaEvent = "UserSubscriptionQuotaEvent" ) // APIKeyMutation represents an operation that mutates the APIKey nodes in the graph. @@ -20817,6 +20819,8 @@ type GroupMutation struct { addweekly_limit_usd *float64 monthly_limit_usd *float64 addmonthly_limit_usd *float64 + total_limit_usd *float64 + addtotal_limit_usd *float64 default_validity_days *int adddefault_validity_days *int allow_image_generation *bool @@ -21819,6 +21823,76 @@ func (m *GroupMutation) ResetMonthlyLimitUsd() { delete(m.clearedFields, group.FieldMonthlyLimitUsd) } +// SetTotalLimitUsd sets the "total_limit_usd" field. +func (m *GroupMutation) SetTotalLimitUsd(f float64) { + m.total_limit_usd = &f + m.addtotal_limit_usd = nil +} + +// TotalLimitUsd returns the value of the "total_limit_usd" field in the mutation. +func (m *GroupMutation) TotalLimitUsd() (r float64, exists bool) { + v := m.total_limit_usd + if v == nil { + return + } + return *v, true +} + +// OldTotalLimitUsd returns the old "total_limit_usd" field's value of the Group entity. +// If the Group object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *GroupMutation) OldTotalLimitUsd(ctx context.Context) (v *float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldTotalLimitUsd is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldTotalLimitUsd requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldTotalLimitUsd: %w", err) + } + return oldValue.TotalLimitUsd, nil +} + +// AddTotalLimitUsd adds f to the "total_limit_usd" field. +func (m *GroupMutation) AddTotalLimitUsd(f float64) { + if m.addtotal_limit_usd != nil { + *m.addtotal_limit_usd += f + } else { + m.addtotal_limit_usd = &f + } +} + +// AddedTotalLimitUsd returns the value that was added to the "total_limit_usd" field in this mutation. +func (m *GroupMutation) AddedTotalLimitUsd() (r float64, exists bool) { + v := m.addtotal_limit_usd + if v == nil { + return + } + return *v, true +} + +// ClearTotalLimitUsd clears the value of the "total_limit_usd" field. +func (m *GroupMutation) ClearTotalLimitUsd() { + m.total_limit_usd = nil + m.addtotal_limit_usd = nil + m.clearedFields[group.FieldTotalLimitUsd] = struct{}{} +} + +// TotalLimitUsdCleared returns if the "total_limit_usd" field was cleared in this mutation. +func (m *GroupMutation) TotalLimitUsdCleared() bool { + _, ok := m.clearedFields[group.FieldTotalLimitUsd] + return ok +} + +// ResetTotalLimitUsd resets all changes to the "total_limit_usd" field. +func (m *GroupMutation) ResetTotalLimitUsd() { + m.total_limit_usd = nil + m.addtotal_limit_usd = nil + delete(m.clearedFields, group.FieldTotalLimitUsd) +} + // SetDefaultValidityDays sets the "default_validity_days" field. func (m *GroupMutation) SetDefaultValidityDays(i int) { m.default_validity_days = &i @@ -23854,7 +23928,7 @@ func (m *GroupMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *GroupMutation) Fields() []string { - fields := make([]string, 0, 51) + fields := make([]string, 0, 52) if m.created_at != nil { fields = append(fields, group.FieldCreatedAt) } @@ -23909,6 +23983,9 @@ func (m *GroupMutation) Fields() []string { if m.monthly_limit_usd != nil { fields = append(fields, group.FieldMonthlyLimitUsd) } + if m.total_limit_usd != nil { + fields = append(fields, group.FieldTotalLimitUsd) + } if m.default_validity_days != nil { fields = append(fields, group.FieldDefaultValidityDays) } @@ -24052,6 +24129,8 @@ func (m *GroupMutation) Field(name string) (ent.Value, bool) { return m.WeeklyLimitUsd() case group.FieldMonthlyLimitUsd: return m.MonthlyLimitUsd() + case group.FieldTotalLimitUsd: + return m.TotalLimitUsd() case group.FieldDefaultValidityDays: return m.DefaultValidityDays() case group.FieldAllowImageGeneration: @@ -24163,6 +24242,8 @@ func (m *GroupMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldWeeklyLimitUsd(ctx) case group.FieldMonthlyLimitUsd: return m.OldMonthlyLimitUsd(ctx) + case group.FieldTotalLimitUsd: + return m.OldTotalLimitUsd(ctx) case group.FieldDefaultValidityDays: return m.OldDefaultValidityDays(ctx) case group.FieldAllowImageGeneration: @@ -24364,6 +24445,13 @@ func (m *GroupMutation) SetField(name string, value ent.Value) error { } m.SetMonthlyLimitUsd(v) return nil + case group.FieldTotalLimitUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetTotalLimitUsd(v) + return nil case group.FieldDefaultValidityDays: v, ok := value.(int) if !ok { @@ -24618,6 +24706,9 @@ func (m *GroupMutation) AddedFields() []string { if m.addmonthly_limit_usd != nil { fields = append(fields, group.FieldMonthlyLimitUsd) } + if m.addtotal_limit_usd != nil { + fields = append(fields, group.FieldTotalLimitUsd) + } if m.adddefault_validity_days != nil { fields = append(fields, group.FieldDefaultValidityDays) } @@ -24684,6 +24775,8 @@ func (m *GroupMutation) AddedField(name string) (ent.Value, bool) { return m.AddedWeeklyLimitUsd() case group.FieldMonthlyLimitUsd: return m.AddedMonthlyLimitUsd() + case group.FieldTotalLimitUsd: + return m.AddedTotalLimitUsd() case group.FieldDefaultValidityDays: return m.AddedDefaultValidityDays() case group.FieldImageRateMultiplier: @@ -24760,6 +24853,13 @@ func (m *GroupMutation) AddField(name string, value ent.Value) error { } m.AddMonthlyLimitUsd(v) return nil + case group.FieldTotalLimitUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddTotalLimitUsd(v) + return nil case group.FieldDefaultValidityDays: v, ok := value.(int) if !ok { @@ -24898,6 +24998,9 @@ func (m *GroupMutation) ClearedFields() []string { if m.FieldCleared(group.FieldMonthlyLimitUsd) { fields = append(fields, group.FieldMonthlyLimitUsd) } + if m.FieldCleared(group.FieldTotalLimitUsd) { + fields = append(fields, group.FieldTotalLimitUsd) + } if m.FieldCleared(group.FieldImagePrice1k) { fields = append(fields, group.FieldImagePrice1k) } @@ -24960,6 +25063,9 @@ func (m *GroupMutation) ClearField(name string) error { case group.FieldMonthlyLimitUsd: m.ClearMonthlyLimitUsd() return nil + case group.FieldTotalLimitUsd: + m.ClearTotalLimitUsd() + return nil case group.FieldImagePrice1k: m.ClearImagePrice1k() return nil @@ -25052,6 +25158,9 @@ func (m *GroupMutation) ResetField(name string) error { case group.FieldMonthlyLimitUsd: m.ResetMonthlyLimitUsd() return nil + case group.FieldTotalLimitUsd: + m.ResetTotalLimitUsd() + return nil case group.FieldDefaultValidityDays: m.ResetDefaultValidityDays() return nil @@ -40937,8 +41046,8 @@ type UsageCleanupTaskMutation struct { created_at *time.Time updated_at *time.Time status *string - filters *json.RawMessage - appendfilters json.RawMessage + filters *jsontext.Value + appendfilters jsontext.Value created_by *int64 addcreated_by *int64 deleted_rows *int64 @@ -41162,13 +41271,13 @@ func (m *UsageCleanupTaskMutation) ResetStatus() { } // SetFilters sets the "filters" field. -func (m *UsageCleanupTaskMutation) SetFilters(jm json.RawMessage) { - m.filters = &jm +func (m *UsageCleanupTaskMutation) SetFilters(j jsontext.Value) { + m.filters = &j m.appendfilters = nil } // Filters returns the value of the "filters" field in the mutation. -func (m *UsageCleanupTaskMutation) Filters() (r json.RawMessage, exists bool) { +func (m *UsageCleanupTaskMutation) Filters() (r jsontext.Value, exists bool) { v := m.filters if v == nil { return @@ -41179,7 +41288,7 @@ func (m *UsageCleanupTaskMutation) Filters() (r json.RawMessage, exists bool) { // OldFilters returns the old "filters" field's value of the UsageCleanupTask entity. // If the UsageCleanupTask object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageCleanupTaskMutation) OldFilters(ctx context.Context) (v json.RawMessage, err error) { +func (m *UsageCleanupTaskMutation) OldFilters(ctx context.Context) (v jsontext.Value, err error) { if !m.op.Is(OpUpdateOne) { return v, errors.New("OldFilters is only allowed on UpdateOne operations") } @@ -41193,13 +41302,13 @@ func (m *UsageCleanupTaskMutation) OldFilters(ctx context.Context) (v json.RawMe return oldValue.Filters, nil } -// AppendFilters adds jm to the "filters" field. -func (m *UsageCleanupTaskMutation) AppendFilters(jm json.RawMessage) { - m.appendfilters = append(m.appendfilters, jm...) +// AppendFilters adds j to the "filters" field. +func (m *UsageCleanupTaskMutation) AppendFilters(j jsontext.Value) { + m.appendfilters = append(m.appendfilters, j...) } // AppendedFilters returns the list of values that were appended to the "filters" field in this mutation. -func (m *UsageCleanupTaskMutation) AppendedFilters() (json.RawMessage, bool) { +func (m *UsageCleanupTaskMutation) AppendedFilters() (jsontext.Value, bool) { if len(m.appendfilters) == 0 { return nil, false } @@ -41750,7 +41859,7 @@ func (m *UsageCleanupTaskMutation) SetField(name string, value ent.Value) error m.SetStatus(v) return nil case usagecleanuptask.FieldFilters: - v, ok := value.(json.RawMessage) + v, ok := value.(jsontext.Value) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } @@ -52682,6 +52791,9 @@ type UserSubscriptionMutation struct { usage_logs map[int64]struct{} removedusage_logs map[int64]struct{} clearedusage_logs bool + quota_events map[int64]struct{} + removedquota_events map[int64]struct{} + clearedquota_events bool done bool oldValue func(context.Context) (*UserSubscription, error) predicates []predicate.UserSubscription @@ -53683,6 +53795,60 @@ func (m *UserSubscriptionMutation) ResetUsageLogs() { m.removedusage_logs = nil } +// AddQuotaEventIDs adds the "quota_events" edge to the UserSubscriptionQuotaEvent entity by ids. +func (m *UserSubscriptionMutation) AddQuotaEventIDs(ids ...int64) { + if m.quota_events == nil { + m.quota_events = make(map[int64]struct{}) + } + for i := range ids { + m.quota_events[ids[i]] = struct{}{} + } +} + +// ClearQuotaEvents clears the "quota_events" edge to the UserSubscriptionQuotaEvent entity. +func (m *UserSubscriptionMutation) ClearQuotaEvents() { + m.clearedquota_events = true +} + +// QuotaEventsCleared reports if the "quota_events" edge to the UserSubscriptionQuotaEvent entity was cleared. +func (m *UserSubscriptionMutation) QuotaEventsCleared() bool { + return m.clearedquota_events +} + +// RemoveQuotaEventIDs removes the "quota_events" edge to the UserSubscriptionQuotaEvent entity by IDs. +func (m *UserSubscriptionMutation) RemoveQuotaEventIDs(ids ...int64) { + if m.removedquota_events == nil { + m.removedquota_events = make(map[int64]struct{}) + } + for i := range ids { + delete(m.quota_events, ids[i]) + m.removedquota_events[ids[i]] = struct{}{} + } +} + +// RemovedQuotaEvents returns the removed IDs of the "quota_events" edge to the UserSubscriptionQuotaEvent entity. +func (m *UserSubscriptionMutation) RemovedQuotaEventsIDs() (ids []int64) { + for id := range m.removedquota_events { + ids = append(ids, id) + } + return +} + +// QuotaEventsIDs returns the "quota_events" edge IDs in the mutation. +func (m *UserSubscriptionMutation) QuotaEventsIDs() (ids []int64) { + for id := range m.quota_events { + ids = append(ids, id) + } + return +} + +// ResetQuotaEvents resets all changes to the "quota_events" edge. +func (m *UserSubscriptionMutation) ResetQuotaEvents() { + m.quota_events = nil + m.clearedquota_events = false + m.removedquota_events = nil +} + // Where appends a list predicates to the UserSubscriptionMutation builder. func (m *UserSubscriptionMutation) Where(ps ...predicate.UserSubscription) { m.predicates = append(m.predicates, ps...) @@ -54166,7 +54332,7 @@ func (m *UserSubscriptionMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserSubscriptionMutation) AddedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.user != nil { edges = append(edges, usersubscription.EdgeUser) } @@ -54179,6 +54345,9 @@ func (m *UserSubscriptionMutation) AddedEdges() []string { if m.usage_logs != nil { edges = append(edges, usersubscription.EdgeUsageLogs) } + if m.quota_events != nil { + edges = append(edges, usersubscription.EdgeQuotaEvents) + } return edges } @@ -54204,16 +54373,25 @@ func (m *UserSubscriptionMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case usersubscription.EdgeQuotaEvents: + ids := make([]ent.Value, 0, len(m.quota_events)) + for id := range m.quota_events { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserSubscriptionMutation) RemovedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.removedusage_logs != nil { edges = append(edges, usersubscription.EdgeUsageLogs) } + if m.removedquota_events != nil { + edges = append(edges, usersubscription.EdgeQuotaEvents) + } return edges } @@ -54227,13 +54405,19 @@ func (m *UserSubscriptionMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case usersubscription.EdgeQuotaEvents: + ids := make([]ent.Value, 0, len(m.removedquota_events)) + for id := range m.removedquota_events { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserSubscriptionMutation) ClearedEdges() []string { - edges := make([]string, 0, 4) + edges := make([]string, 0, 5) if m.cleareduser { edges = append(edges, usersubscription.EdgeUser) } @@ -54246,6 +54430,9 @@ func (m *UserSubscriptionMutation) ClearedEdges() []string { if m.clearedusage_logs { edges = append(edges, usersubscription.EdgeUsageLogs) } + if m.clearedquota_events { + edges = append(edges, usersubscription.EdgeQuotaEvents) + } return edges } @@ -54261,6 +54448,8 @@ func (m *UserSubscriptionMutation) EdgeCleared(name string) bool { return m.clearedassigned_by_user case usersubscription.EdgeUsageLogs: return m.clearedusage_logs + case usersubscription.EdgeQuotaEvents: + return m.clearedquota_events } return false } @@ -54298,6 +54487,925 @@ func (m *UserSubscriptionMutation) ResetEdge(name string) error { case usersubscription.EdgeUsageLogs: m.ResetUsageLogs() return nil + case usersubscription.EdgeQuotaEvents: + m.ResetQuotaEvents() + return nil } return fmt.Errorf("unknown UserSubscription edge %s", name) } + +// UserSubscriptionQuotaEventMutation represents an operation that mutates the UserSubscriptionQuotaEvent nodes in the graph. +type UserSubscriptionQuotaEventMutation struct { + config + op Op + typ string + id *int64 + quota_total_usd *float64 + addquota_total_usd *float64 + quota_used_usd *float64 + addquota_used_usd *float64 + starts_at *time.Time + expires_at *time.Time + source_kind *string + source_ref *string + created_at *time.Time + updated_at *time.Time + clearedFields map[string]struct{} + subscription *int64 + clearedsubscription bool + done bool + oldValue func(context.Context) (*UserSubscriptionQuotaEvent, error) + predicates []predicate.UserSubscriptionQuotaEvent +} + +var _ ent.Mutation = (*UserSubscriptionQuotaEventMutation)(nil) + +// usersubscriptionquotaeventOption allows management of the mutation configuration using functional options. +type usersubscriptionquotaeventOption func(*UserSubscriptionQuotaEventMutation) + +// newUserSubscriptionQuotaEventMutation creates new mutation for the UserSubscriptionQuotaEvent entity. +func newUserSubscriptionQuotaEventMutation(c config, op Op, opts ...usersubscriptionquotaeventOption) *UserSubscriptionQuotaEventMutation { + m := &UserSubscriptionQuotaEventMutation{ + config: c, + op: op, + typ: TypeUserSubscriptionQuotaEvent, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withUserSubscriptionQuotaEventID sets the ID field of the mutation. +func withUserSubscriptionQuotaEventID(id int64) usersubscriptionquotaeventOption { + return func(m *UserSubscriptionQuotaEventMutation) { + var ( + err error + once sync.Once + value *UserSubscriptionQuotaEvent + ) + m.oldValue = func(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().UserSubscriptionQuotaEvent.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withUserSubscriptionQuotaEvent sets the old UserSubscriptionQuotaEvent of the mutation. +func withUserSubscriptionQuotaEvent(node *UserSubscriptionQuotaEvent) usersubscriptionquotaeventOption { + return func(m *UserSubscriptionQuotaEventMutation) { + m.oldValue = func(context.Context) (*UserSubscriptionQuotaEvent, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m UserSubscriptionQuotaEventMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m UserSubscriptionQuotaEventMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *UserSubscriptionQuotaEventMutation) ID() (id int64, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *UserSubscriptionQuotaEventMutation) IDs(ctx context.Context) ([]int64, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int64{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().UserSubscriptionQuotaEvent.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (m *UserSubscriptionQuotaEventMutation) SetUserSubscriptionID(i int64) { + m.subscription = &i +} + +// UserSubscriptionID returns the value of the "user_subscription_id" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) UserSubscriptionID() (r int64, exists bool) { + v := m.subscription + if v == nil { + return + } + return *v, true +} + +// OldUserSubscriptionID returns the old "user_subscription_id" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldUserSubscriptionID(ctx context.Context) (v int64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUserSubscriptionID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUserSubscriptionID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUserSubscriptionID: %w", err) + } + return oldValue.UserSubscriptionID, nil +} + +// ResetUserSubscriptionID resets all changes to the "user_subscription_id" field. +func (m *UserSubscriptionQuotaEventMutation) ResetUserSubscriptionID() { + m.subscription = nil +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (m *UserSubscriptionQuotaEventMutation) SetQuotaTotalUsd(f float64) { + m.quota_total_usd = &f + m.addquota_total_usd = nil +} + +// QuotaTotalUsd returns the value of the "quota_total_usd" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) QuotaTotalUsd() (r float64, exists bool) { + v := m.quota_total_usd + if v == nil { + return + } + return *v, true +} + +// OldQuotaTotalUsd returns the old "quota_total_usd" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldQuotaTotalUsd(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldQuotaTotalUsd is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldQuotaTotalUsd requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldQuotaTotalUsd: %w", err) + } + return oldValue.QuotaTotalUsd, nil +} + +// AddQuotaTotalUsd adds f to the "quota_total_usd" field. +func (m *UserSubscriptionQuotaEventMutation) AddQuotaTotalUsd(f float64) { + if m.addquota_total_usd != nil { + *m.addquota_total_usd += f + } else { + m.addquota_total_usd = &f + } +} + +// AddedQuotaTotalUsd returns the value that was added to the "quota_total_usd" field in this mutation. +func (m *UserSubscriptionQuotaEventMutation) AddedQuotaTotalUsd() (r float64, exists bool) { + v := m.addquota_total_usd + if v == nil { + return + } + return *v, true +} + +// ResetQuotaTotalUsd resets all changes to the "quota_total_usd" field. +func (m *UserSubscriptionQuotaEventMutation) ResetQuotaTotalUsd() { + m.quota_total_usd = nil + m.addquota_total_usd = nil +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (m *UserSubscriptionQuotaEventMutation) SetQuotaUsedUsd(f float64) { + m.quota_used_usd = &f + m.addquota_used_usd = nil +} + +// QuotaUsedUsd returns the value of the "quota_used_usd" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) QuotaUsedUsd() (r float64, exists bool) { + v := m.quota_used_usd + if v == nil { + return + } + return *v, true +} + +// OldQuotaUsedUsd returns the old "quota_used_usd" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldQuotaUsedUsd(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldQuotaUsedUsd is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldQuotaUsedUsd requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldQuotaUsedUsd: %w", err) + } + return oldValue.QuotaUsedUsd, nil +} + +// AddQuotaUsedUsd adds f to the "quota_used_usd" field. +func (m *UserSubscriptionQuotaEventMutation) AddQuotaUsedUsd(f float64) { + if m.addquota_used_usd != nil { + *m.addquota_used_usd += f + } else { + m.addquota_used_usd = &f + } +} + +// AddedQuotaUsedUsd returns the value that was added to the "quota_used_usd" field in this mutation. +func (m *UserSubscriptionQuotaEventMutation) AddedQuotaUsedUsd() (r float64, exists bool) { + v := m.addquota_used_usd + if v == nil { + return + } + return *v, true +} + +// ResetQuotaUsedUsd resets all changes to the "quota_used_usd" field. +func (m *UserSubscriptionQuotaEventMutation) ResetQuotaUsedUsd() { + m.quota_used_usd = nil + m.addquota_used_usd = nil +} + +// SetStartsAt sets the "starts_at" field. +func (m *UserSubscriptionQuotaEventMutation) SetStartsAt(t time.Time) { + m.starts_at = &t +} + +// StartsAt returns the value of the "starts_at" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) StartsAt() (r time.Time, exists bool) { + v := m.starts_at + if v == nil { + return + } + return *v, true +} + +// OldStartsAt returns the old "starts_at" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldStartsAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldStartsAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldStartsAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldStartsAt: %w", err) + } + return oldValue.StartsAt, nil +} + +// ResetStartsAt resets all changes to the "starts_at" field. +func (m *UserSubscriptionQuotaEventMutation) ResetStartsAt() { + m.starts_at = nil +} + +// SetExpiresAt sets the "expires_at" field. +func (m *UserSubscriptionQuotaEventMutation) SetExpiresAt(t time.Time) { + m.expires_at = &t +} + +// ExpiresAt returns the value of the "expires_at" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) ExpiresAt() (r time.Time, exists bool) { + v := m.expires_at + if v == nil { + return + } + return *v, true +} + +// OldExpiresAt returns the old "expires_at" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldExpiresAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldExpiresAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err) + } + return oldValue.ExpiresAt, nil +} + +// ResetExpiresAt resets all changes to the "expires_at" field. +func (m *UserSubscriptionQuotaEventMutation) ResetExpiresAt() { + m.expires_at = nil +} + +// SetSourceKind sets the "source_kind" field. +func (m *UserSubscriptionQuotaEventMutation) SetSourceKind(s string) { + m.source_kind = &s +} + +// SourceKind returns the value of the "source_kind" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) SourceKind() (r string, exists bool) { + v := m.source_kind + if v == nil { + return + } + return *v, true +} + +// OldSourceKind returns the old "source_kind" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldSourceKind(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceKind is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceKind requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceKind: %w", err) + } + return oldValue.SourceKind, nil +} + +// ResetSourceKind resets all changes to the "source_kind" field. +func (m *UserSubscriptionQuotaEventMutation) ResetSourceKind() { + m.source_kind = nil +} + +// SetSourceRef sets the "source_ref" field. +func (m *UserSubscriptionQuotaEventMutation) SetSourceRef(s string) { + m.source_ref = &s +} + +// SourceRef returns the value of the "source_ref" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) SourceRef() (r string, exists bool) { + v := m.source_ref + if v == nil { + return + } + return *v, true +} + +// OldSourceRef returns the old "source_ref" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldSourceRef(ctx context.Context) (v *string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSourceRef is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSourceRef requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSourceRef: %w", err) + } + return oldValue.SourceRef, nil +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (m *UserSubscriptionQuotaEventMutation) ClearSourceRef() { + m.source_ref = nil + m.clearedFields[usersubscriptionquotaevent.FieldSourceRef] = struct{}{} +} + +// SourceRefCleared returns if the "source_ref" field was cleared in this mutation. +func (m *UserSubscriptionQuotaEventMutation) SourceRefCleared() bool { + _, ok := m.clearedFields[usersubscriptionquotaevent.FieldSourceRef] + return ok +} + +// ResetSourceRef resets all changes to the "source_ref" field. +func (m *UserSubscriptionQuotaEventMutation) ResetSourceRef() { + m.source_ref = nil + delete(m.clearedFields, usersubscriptionquotaevent.FieldSourceRef) +} + +// SetCreatedAt sets the "created_at" field. +func (m *UserSubscriptionQuotaEventMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *UserSubscriptionQuotaEventMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *UserSubscriptionQuotaEventMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *UserSubscriptionQuotaEventMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the UserSubscriptionQuotaEvent entity. +// If the UserSubscriptionQuotaEvent object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *UserSubscriptionQuotaEventMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *UserSubscriptionQuotaEventMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetSubscriptionID sets the "subscription" edge to the UserSubscription entity by id. +func (m *UserSubscriptionQuotaEventMutation) SetSubscriptionID(id int64) { + m.subscription = &id +} + +// ClearSubscription clears the "subscription" edge to the UserSubscription entity. +func (m *UserSubscriptionQuotaEventMutation) ClearSubscription() { + m.clearedsubscription = true + m.clearedFields[usersubscriptionquotaevent.FieldUserSubscriptionID] = struct{}{} +} + +// SubscriptionCleared reports if the "subscription" edge to the UserSubscription entity was cleared. +func (m *UserSubscriptionQuotaEventMutation) SubscriptionCleared() bool { + return m.clearedsubscription +} + +// SubscriptionID returns the "subscription" edge ID in the mutation. +func (m *UserSubscriptionQuotaEventMutation) SubscriptionID() (id int64, exists bool) { + if m.subscription != nil { + return *m.subscription, true + } + return +} + +// SubscriptionIDs returns the "subscription" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// SubscriptionID instead. It exists only for internal usage by the builders. +func (m *UserSubscriptionQuotaEventMutation) SubscriptionIDs() (ids []int64) { + if id := m.subscription; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetSubscription resets all changes to the "subscription" edge. +func (m *UserSubscriptionQuotaEventMutation) ResetSubscription() { + m.subscription = nil + m.clearedsubscription = false +} + +// Where appends a list predicates to the UserSubscriptionQuotaEventMutation builder. +func (m *UserSubscriptionQuotaEventMutation) Where(ps ...predicate.UserSubscriptionQuotaEvent) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the UserSubscriptionQuotaEventMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *UserSubscriptionQuotaEventMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.UserSubscriptionQuotaEvent, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *UserSubscriptionQuotaEventMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *UserSubscriptionQuotaEventMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (UserSubscriptionQuotaEvent). +func (m *UserSubscriptionQuotaEventMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *UserSubscriptionQuotaEventMutation) Fields() []string { + fields := make([]string, 0, 9) + if m.subscription != nil { + fields = append(fields, usersubscriptionquotaevent.FieldUserSubscriptionID) + } + if m.quota_total_usd != nil { + fields = append(fields, usersubscriptionquotaevent.FieldQuotaTotalUsd) + } + if m.quota_used_usd != nil { + fields = append(fields, usersubscriptionquotaevent.FieldQuotaUsedUsd) + } + if m.starts_at != nil { + fields = append(fields, usersubscriptionquotaevent.FieldStartsAt) + } + if m.expires_at != nil { + fields = append(fields, usersubscriptionquotaevent.FieldExpiresAt) + } + if m.source_kind != nil { + fields = append(fields, usersubscriptionquotaevent.FieldSourceKind) + } + if m.source_ref != nil { + fields = append(fields, usersubscriptionquotaevent.FieldSourceRef) + } + if m.created_at != nil { + fields = append(fields, usersubscriptionquotaevent.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, usersubscriptionquotaevent.FieldUpdatedAt) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *UserSubscriptionQuotaEventMutation) Field(name string) (ent.Value, bool) { + switch name { + case usersubscriptionquotaevent.FieldUserSubscriptionID: + return m.UserSubscriptionID() + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + return m.QuotaTotalUsd() + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + return m.QuotaUsedUsd() + case usersubscriptionquotaevent.FieldStartsAt: + return m.StartsAt() + case usersubscriptionquotaevent.FieldExpiresAt: + return m.ExpiresAt() + case usersubscriptionquotaevent.FieldSourceKind: + return m.SourceKind() + case usersubscriptionquotaevent.FieldSourceRef: + return m.SourceRef() + case usersubscriptionquotaevent.FieldCreatedAt: + return m.CreatedAt() + case usersubscriptionquotaevent.FieldUpdatedAt: + return m.UpdatedAt() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *UserSubscriptionQuotaEventMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case usersubscriptionquotaevent.FieldUserSubscriptionID: + return m.OldUserSubscriptionID(ctx) + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + return m.OldQuotaTotalUsd(ctx) + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + return m.OldQuotaUsedUsd(ctx) + case usersubscriptionquotaevent.FieldStartsAt: + return m.OldStartsAt(ctx) + case usersubscriptionquotaevent.FieldExpiresAt: + return m.OldExpiresAt(ctx) + case usersubscriptionquotaevent.FieldSourceKind: + return m.OldSourceKind(ctx) + case usersubscriptionquotaevent.FieldSourceRef: + return m.OldSourceRef(ctx) + case usersubscriptionquotaevent.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case usersubscriptionquotaevent.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + } + return nil, fmt.Errorf("unknown UserSubscriptionQuotaEvent field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserSubscriptionQuotaEventMutation) SetField(name string, value ent.Value) error { + switch name { + case usersubscriptionquotaevent.FieldUserSubscriptionID: + v, ok := value.(int64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUserSubscriptionID(v) + return nil + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetQuotaTotalUsd(v) + return nil + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetQuotaUsedUsd(v) + return nil + case usersubscriptionquotaevent.FieldStartsAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetStartsAt(v) + return nil + case usersubscriptionquotaevent.FieldExpiresAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetExpiresAt(v) + return nil + case usersubscriptionquotaevent.FieldSourceKind: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceKind(v) + return nil + case usersubscriptionquotaevent.FieldSourceRef: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSourceRef(v) + return nil + case usersubscriptionquotaevent.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case usersubscriptionquotaevent.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *UserSubscriptionQuotaEventMutation) AddedFields() []string { + var fields []string + if m.addquota_total_usd != nil { + fields = append(fields, usersubscriptionquotaevent.FieldQuotaTotalUsd) + } + if m.addquota_used_usd != nil { + fields = append(fields, usersubscriptionquotaevent.FieldQuotaUsedUsd) + } + return fields +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *UserSubscriptionQuotaEventMutation) AddedField(name string) (ent.Value, bool) { + switch name { + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + return m.AddedQuotaTotalUsd() + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + return m.AddedQuotaUsedUsd() + } + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *UserSubscriptionQuotaEventMutation) AddField(name string, value ent.Value) error { + switch name { + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddQuotaTotalUsd(v) + return nil + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddQuotaUsedUsd(v) + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *UserSubscriptionQuotaEventMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(usersubscriptionquotaevent.FieldSourceRef) { + fields = append(fields, usersubscriptionquotaevent.FieldSourceRef) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *UserSubscriptionQuotaEventMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *UserSubscriptionQuotaEventMutation) ClearField(name string) error { + switch name { + case usersubscriptionquotaevent.FieldSourceRef: + m.ClearSourceRef() + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *UserSubscriptionQuotaEventMutation) ResetField(name string) error { + switch name { + case usersubscriptionquotaevent.FieldUserSubscriptionID: + m.ResetUserSubscriptionID() + return nil + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + m.ResetQuotaTotalUsd() + return nil + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + m.ResetQuotaUsedUsd() + return nil + case usersubscriptionquotaevent.FieldStartsAt: + m.ResetStartsAt() + return nil + case usersubscriptionquotaevent.FieldExpiresAt: + m.ResetExpiresAt() + return nil + case usersubscriptionquotaevent.FieldSourceKind: + m.ResetSourceKind() + return nil + case usersubscriptionquotaevent.FieldSourceRef: + m.ResetSourceRef() + return nil + case usersubscriptionquotaevent.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case usersubscriptionquotaevent.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *UserSubscriptionQuotaEventMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.subscription != nil { + edges = append(edges, usersubscriptionquotaevent.EdgeSubscription) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *UserSubscriptionQuotaEventMutation) AddedIDs(name string) []ent.Value { + switch name { + case usersubscriptionquotaevent.EdgeSubscription: + if id := m.subscription; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *UserSubscriptionQuotaEventMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *UserSubscriptionQuotaEventMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *UserSubscriptionQuotaEventMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedsubscription { + edges = append(edges, usersubscriptionquotaevent.EdgeSubscription) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *UserSubscriptionQuotaEventMutation) EdgeCleared(name string) bool { + switch name { + case usersubscriptionquotaevent.EdgeSubscription: + return m.clearedsubscription + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *UserSubscriptionQuotaEventMutation) ClearEdge(name string) error { + switch name { + case usersubscriptionquotaevent.EdgeSubscription: + m.ClearSubscription() + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *UserSubscriptionQuotaEventMutation) ResetEdge(name string) error { + switch name { + case usersubscriptionquotaevent.EdgeSubscription: + m.ResetSubscription() + return nil + } + return fmt.Errorf("unknown UserSubscriptionQuotaEvent edge %s", name) +} diff --git a/backend/ent/predicate/predicate.go b/backend/ent/predicate/predicate.go index 8d18d38151a..a172fb375f6 100644 --- a/backend/ent/predicate/predicate.go +++ b/backend/ent/predicate/predicate.go @@ -119,3 +119,6 @@ type UserPlatformQuota func(*sql.Selector) // UserSubscription is the predicate function for usersubscription builders. type UserSubscription func(*sql.Selector) + +// UserSubscriptionQuotaEvent is the predicate function for usersubscriptionquotaevent builders. +type UserSubscriptionQuotaEvent func(*sql.Selector) diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index b1dcc8c5853..5771991a0dc 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -44,6 +44,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/userattributevalue" "github.com/Wei-Shaw/sub2api/ent/userplatformquota" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" "github.com/Wei-Shaw/sub2api/internal/domain" ) @@ -1012,99 +1013,99 @@ func init() { // group.SubscriptionTypeValidator is a validator for the "subscription_type" field. It is called by the builders before save. group.SubscriptionTypeValidator = groupDescSubscriptionType.Validators[0].(func(string) error) // groupDescDefaultValidityDays is the schema descriptor for default_validity_days field. - groupDescDefaultValidityDays := groupFields[15].Descriptor() + groupDescDefaultValidityDays := groupFields[16].Descriptor() // group.DefaultDefaultValidityDays holds the default value on creation for the default_validity_days field. group.DefaultDefaultValidityDays = groupDescDefaultValidityDays.Default.(int) // groupDescAllowImageGeneration is the schema descriptor for allow_image_generation field. - groupDescAllowImageGeneration := groupFields[16].Descriptor() + groupDescAllowImageGeneration := groupFields[17].Descriptor() // group.DefaultAllowImageGeneration holds the default value on creation for the allow_image_generation field. group.DefaultAllowImageGeneration = groupDescAllowImageGeneration.Default.(bool) // groupDescAllowBatchImageGeneration is the schema descriptor for allow_batch_image_generation field. - groupDescAllowBatchImageGeneration := groupFields[17].Descriptor() + groupDescAllowBatchImageGeneration := groupFields[18].Descriptor() // group.DefaultAllowBatchImageGeneration holds the default value on creation for the allow_batch_image_generation field. group.DefaultAllowBatchImageGeneration = groupDescAllowBatchImageGeneration.Default.(bool) // groupDescImageRateIndependent is the schema descriptor for image_rate_independent field. - groupDescImageRateIndependent := groupFields[18].Descriptor() + groupDescImageRateIndependent := groupFields[19].Descriptor() // group.DefaultImageRateIndependent holds the default value on creation for the image_rate_independent field. group.DefaultImageRateIndependent = groupDescImageRateIndependent.Default.(bool) // groupDescImageRateMultiplier is the schema descriptor for image_rate_multiplier field. - groupDescImageRateMultiplier := groupFields[19].Descriptor() + groupDescImageRateMultiplier := groupFields[20].Descriptor() // group.DefaultImageRateMultiplier holds the default value on creation for the image_rate_multiplier field. group.DefaultImageRateMultiplier = groupDescImageRateMultiplier.Default.(float64) // groupDescBatchImageDiscountMultiplier is the schema descriptor for batch_image_discount_multiplier field. - groupDescBatchImageDiscountMultiplier := groupFields[23].Descriptor() + groupDescBatchImageDiscountMultiplier := groupFields[24].Descriptor() // group.DefaultBatchImageDiscountMultiplier holds the default value on creation for the batch_image_discount_multiplier field. group.DefaultBatchImageDiscountMultiplier = groupDescBatchImageDiscountMultiplier.Default.(float64) // groupDescBatchImageHoldMultiplier is the schema descriptor for batch_image_hold_multiplier field. - groupDescBatchImageHoldMultiplier := groupFields[24].Descriptor() + groupDescBatchImageHoldMultiplier := groupFields[25].Descriptor() // group.DefaultBatchImageHoldMultiplier holds the default value on creation for the batch_image_hold_multiplier field. group.DefaultBatchImageHoldMultiplier = groupDescBatchImageHoldMultiplier.Default.(float64) // groupDescVideoRateIndependent is the schema descriptor for video_rate_independent field. - groupDescVideoRateIndependent := groupFields[25].Descriptor() + groupDescVideoRateIndependent := groupFields[26].Descriptor() // group.DefaultVideoRateIndependent holds the default value on creation for the video_rate_independent field. group.DefaultVideoRateIndependent = groupDescVideoRateIndependent.Default.(bool) // groupDescVideoRateMultiplier is the schema descriptor for video_rate_multiplier field. - groupDescVideoRateMultiplier := groupFields[26].Descriptor() + groupDescVideoRateMultiplier := groupFields[27].Descriptor() // group.DefaultVideoRateMultiplier holds the default value on creation for the video_rate_multiplier field. group.DefaultVideoRateMultiplier = groupDescVideoRateMultiplier.Default.(float64) // groupDescClaudeCodeOnly is the schema descriptor for claude_code_only field. - groupDescClaudeCodeOnly := groupFields[31].Descriptor() + groupDescClaudeCodeOnly := groupFields[32].Descriptor() // group.DefaultClaudeCodeOnly holds the default value on creation for the claude_code_only field. group.DefaultClaudeCodeOnly = groupDescClaudeCodeOnly.Default.(bool) // groupDescModelRoutingEnabled is the schema descriptor for model_routing_enabled field. - groupDescModelRoutingEnabled := groupFields[35].Descriptor() + groupDescModelRoutingEnabled := groupFields[36].Descriptor() // group.DefaultModelRoutingEnabled holds the default value on creation for the model_routing_enabled field. group.DefaultModelRoutingEnabled = groupDescModelRoutingEnabled.Default.(bool) // groupDescMcpXMLInject is the schema descriptor for mcp_xml_inject field. - groupDescMcpXMLInject := groupFields[36].Descriptor() + groupDescMcpXMLInject := groupFields[37].Descriptor() // group.DefaultMcpXMLInject holds the default value on creation for the mcp_xml_inject field. group.DefaultMcpXMLInject = groupDescMcpXMLInject.Default.(bool) // groupDescSupportedModelScopes is the schema descriptor for supported_model_scopes field. - groupDescSupportedModelScopes := groupFields[37].Descriptor() + groupDescSupportedModelScopes := groupFields[38].Descriptor() // group.DefaultSupportedModelScopes holds the default value on creation for the supported_model_scopes field. group.DefaultSupportedModelScopes = groupDescSupportedModelScopes.Default.([]string) // groupDescSortOrder is the schema descriptor for sort_order field. - groupDescSortOrder := groupFields[38].Descriptor() + groupDescSortOrder := groupFields[39].Descriptor() // group.DefaultSortOrder holds the default value on creation for the sort_order field. group.DefaultSortOrder = groupDescSortOrder.Default.(int) // groupDescAllowMessagesDispatch is the schema descriptor for allow_messages_dispatch field. - groupDescAllowMessagesDispatch := groupFields[39].Descriptor() + groupDescAllowMessagesDispatch := groupFields[40].Descriptor() // group.DefaultAllowMessagesDispatch holds the default value on creation for the allow_messages_dispatch field. group.DefaultAllowMessagesDispatch = groupDescAllowMessagesDispatch.Default.(bool) // groupDescRequireOauthOnly is the schema descriptor for require_oauth_only field. - groupDescRequireOauthOnly := groupFields[40].Descriptor() + groupDescRequireOauthOnly := groupFields[41].Descriptor() // group.DefaultRequireOauthOnly holds the default value on creation for the require_oauth_only field. group.DefaultRequireOauthOnly = groupDescRequireOauthOnly.Default.(bool) // groupDescRequirePrivacySet is the schema descriptor for require_privacy_set field. - groupDescRequirePrivacySet := groupFields[41].Descriptor() + groupDescRequirePrivacySet := groupFields[42].Descriptor() // group.DefaultRequirePrivacySet holds the default value on creation for the require_privacy_set field. group.DefaultRequirePrivacySet = groupDescRequirePrivacySet.Default.(bool) // groupDescDefaultMappedModel is the schema descriptor for default_mapped_model field. - groupDescDefaultMappedModel := groupFields[42].Descriptor() + groupDescDefaultMappedModel := groupFields[43].Descriptor() // group.DefaultDefaultMappedModel holds the default value on creation for the default_mapped_model field. group.DefaultDefaultMappedModel = groupDescDefaultMappedModel.Default.(string) // group.DefaultMappedModelValidator is a validator for the "default_mapped_model" field. It is called by the builders before save. group.DefaultMappedModelValidator = groupDescDefaultMappedModel.Validators[0].(func(string) error) // groupDescMessagesDispatchModelConfig is the schema descriptor for messages_dispatch_model_config field. - groupDescMessagesDispatchModelConfig := groupFields[43].Descriptor() + groupDescMessagesDispatchModelConfig := groupFields[44].Descriptor() // group.DefaultMessagesDispatchModelConfig holds the default value on creation for the messages_dispatch_model_config field. group.DefaultMessagesDispatchModelConfig = groupDescMessagesDispatchModelConfig.Default.(domain.OpenAIMessagesDispatchModelConfig) // groupDescModelsListConfig is the schema descriptor for models_list_config field. - groupDescModelsListConfig := groupFields[44].Descriptor() + groupDescModelsListConfig := groupFields[45].Descriptor() // group.DefaultModelsListConfig holds the default value on creation for the models_list_config field. group.DefaultModelsListConfig = groupDescModelsListConfig.Default.(domain.GroupModelsListConfig) // groupDescRpmLimit is the schema descriptor for rpm_limit field. - groupDescRpmLimit := groupFields[45].Descriptor() + groupDescRpmLimit := groupFields[46].Descriptor() // group.DefaultRpmLimit holds the default value on creation for the rpm_limit field. group.DefaultRpmLimit = groupDescRpmLimit.Default.(int) // groupDescMaxReasoningEffort is the schema descriptor for max_reasoning_effort field. - groupDescMaxReasoningEffort := groupFields[46].Descriptor() + groupDescMaxReasoningEffort := groupFields[47].Descriptor() // group.DefaultMaxReasoningEffort holds the default value on creation for the max_reasoning_effort field. group.DefaultMaxReasoningEffort = groupDescMaxReasoningEffort.Default.(string) // group.MaxReasoningEffortValidator is a validator for the "max_reasoning_effort" field. It is called by the builders before save. group.MaxReasoningEffortValidator = groupDescMaxReasoningEffort.Validators[0].(func(string) error) // groupDescReasoningEffortMappings is the schema descriptor for reasoning_effort_mappings field. - groupDescReasoningEffortMappings := groupFields[47].Descriptor() + groupDescReasoningEffortMappings := groupFields[48].Descriptor() // group.DefaultReasoningEffortMappings holds the default value on creation for the reasoning_effort_mappings field. group.DefaultReasoningEffortMappings = groupDescReasoningEffortMappings.Default.([]domain.ReasoningEffortMapping) idempotencyrecordMixin := schema.IdempotencyRecord{}.Mixin() @@ -2354,6 +2355,26 @@ func init() { usersubscriptionDescAssignedAt := usersubscriptionFields[12].Descriptor() // usersubscription.DefaultAssignedAt holds the default value on creation for the assigned_at field. usersubscription.DefaultAssignedAt = usersubscriptionDescAssignedAt.Default.(func() time.Time) + usersubscriptionquotaeventFields := schema.UserSubscriptionQuotaEvent{}.Fields() + _ = usersubscriptionquotaeventFields + // usersubscriptionquotaeventDescQuotaUsedUsd is the schema descriptor for quota_used_usd field. + usersubscriptionquotaeventDescQuotaUsedUsd := usersubscriptionquotaeventFields[2].Descriptor() + // usersubscriptionquotaevent.DefaultQuotaUsedUsd holds the default value on creation for the quota_used_usd field. + usersubscriptionquotaevent.DefaultQuotaUsedUsd = usersubscriptionquotaeventDescQuotaUsedUsd.Default.(float64) + // usersubscriptionquotaeventDescSourceKind is the schema descriptor for source_kind field. + usersubscriptionquotaeventDescSourceKind := usersubscriptionquotaeventFields[5].Descriptor() + // usersubscriptionquotaevent.SourceKindValidator is a validator for the "source_kind" field. It is called by the builders before save. + usersubscriptionquotaevent.SourceKindValidator = usersubscriptionquotaeventDescSourceKind.Validators[0].(func(string) error) + // usersubscriptionquotaeventDescCreatedAt is the schema descriptor for created_at field. + usersubscriptionquotaeventDescCreatedAt := usersubscriptionquotaeventFields[7].Descriptor() + // usersubscriptionquotaevent.DefaultCreatedAt holds the default value on creation for the created_at field. + usersubscriptionquotaevent.DefaultCreatedAt = usersubscriptionquotaeventDescCreatedAt.Default.(func() time.Time) + // usersubscriptionquotaeventDescUpdatedAt is the schema descriptor for updated_at field. + usersubscriptionquotaeventDescUpdatedAt := usersubscriptionquotaeventFields[8].Descriptor() + // usersubscriptionquotaevent.DefaultUpdatedAt holds the default value on creation for the updated_at field. + usersubscriptionquotaevent.DefaultUpdatedAt = usersubscriptionquotaeventDescUpdatedAt.Default.(func() time.Time) + // usersubscriptionquotaevent.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + usersubscriptionquotaevent.UpdateDefaultUpdatedAt = usersubscriptionquotaeventDescUpdatedAt.UpdateDefault.(func() time.Time) } const ( diff --git a/backend/ent/runtime/runtime_smoke_test.go b/backend/ent/runtime/runtime_smoke_test.go new file mode 100644 index 00000000000..c52669529c2 --- /dev/null +++ b/backend/ent/runtime/runtime_smoke_test.go @@ -0,0 +1,9 @@ +package runtime_test + +import ( + "testing" + + _ "github.com/Wei-Shaw/sub2api/ent/runtime" +) + +func TestRuntimeInitSmoke(t *testing.T) {} diff --git a/backend/ent/schema/group.go b/backend/ent/schema/group.go index bce09c1f8a0..983ca9ef3c1 100644 --- a/backend/ent/schema/group.go +++ b/backend/ent/schema/group.go @@ -92,6 +92,10 @@ func (Group) Fields() []ent.Field { Optional(). Nillable(). SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), + field.Float("total_limit_usd"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), field.Int("default_validity_days"). Default(30), diff --git a/backend/ent/schema/user_subscription.go b/backend/ent/schema/user_subscription.go index a81850b120b..e1da9c2462d 100644 --- a/backend/ent/schema/user_subscription.go +++ b/backend/ent/schema/user_subscription.go @@ -99,6 +99,7 @@ func (UserSubscription) Edges() []ent.Edge { Field("assigned_by"). Unique(), edge.To("usage_logs", UsageLog.Type), + edge.To("quota_events", UserSubscriptionQuotaEvent.Type), } } diff --git a/backend/ent/schema/user_subscription_quota_event.go b/backend/ent/schema/user_subscription_quota_event.go new file mode 100644 index 00000000000..1899527b4cb --- /dev/null +++ b/backend/ent/schema/user_subscription_quota_event.go @@ -0,0 +1,72 @@ +package schema + +import ( + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/entsql" + "entgo.io/ent/schema" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" +) + +// UserSubscriptionQuotaEvent stores one total-quota subscription event snapshot. +type UserSubscriptionQuotaEvent struct { + ent.Schema +} + +func (UserSubscriptionQuotaEvent) Annotations() []schema.Annotation { + return []schema.Annotation{ + entsql.Annotation{Table: "user_subscription_quota_events"}, + } +} + +func (UserSubscriptionQuotaEvent) Fields() []ent.Field { + return []ent.Field{ + field.Int64("user_subscription_id"), + field.Float("quota_total_usd"). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}), + field.Float("quota_used_usd"). + Default(0). + SchemaType(map[string]string{dialect.Postgres: "decimal(20,10)"}), + field.Time("starts_at"). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Time("expires_at"). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.String("source_kind"). + MaxLen(32), + field.String("source_ref"). + Optional(). + Nillable(). + SchemaType(map[string]string{dialect.Postgres: "text"}), + field.Time("created_at"). + Immutable(). + Default(time.Now). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + field.Time("updated_at"). + Default(time.Now). + UpdateDefault(time.Now). + SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), + } +} + +func (UserSubscriptionQuotaEvent) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("subscription", UserSubscription.Type). + Ref("quota_events"). + Field("user_subscription_id"). + Unique(). + Required(), + } +} + +func (UserSubscriptionQuotaEvent) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("user_subscription_id"), + index.Fields("expires_at"), + index.Fields("user_subscription_id", "expires_at"), + index.Fields("user_subscription_id", "expires_at", "id"), + } +} diff --git a/backend/ent/tx.go b/backend/ent/tx.go index 6de2c2b63b7..0ee07b62165 100644 --- a/backend/ent/tx.go +++ b/backend/ent/tx.go @@ -90,6 +90,8 @@ type Tx struct { UserPlatformQuota *UserPlatformQuotaClient // UserSubscription is the client for interacting with the UserSubscription builders. UserSubscription *UserSubscriptionClient + // UserSubscriptionQuotaEvent is the client for interacting with the UserSubscriptionQuotaEvent builders. + UserSubscriptionQuotaEvent *UserSubscriptionQuotaEventClient // lazily loaded. client *Client @@ -259,6 +261,7 @@ func (tx *Tx) init() { tx.UserAttributeValue = NewUserAttributeValueClient(tx.config) tx.UserPlatformQuota = NewUserPlatformQuotaClient(tx.config) tx.UserSubscription = NewUserSubscriptionClient(tx.config) + tx.UserSubscriptionQuotaEvent = NewUserSubscriptionQuotaEventClient(tx.config) } // txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation. diff --git a/backend/ent/usagecleanuptask.go b/backend/ent/usagecleanuptask.go index e3a17b5aed1..b4dbf0fd342 100644 --- a/backend/ent/usagecleanuptask.go +++ b/backend/ent/usagecleanuptask.go @@ -4,6 +4,7 @@ package ent import ( "encoding/json" + "encoding/json/jsontext" "fmt" "strings" "time" @@ -25,7 +26,7 @@ type UsageCleanupTask struct { // Status holds the value of the "status" field. Status string `json:"status,omitempty"` // Filters holds the value of the "filters" field. - Filters json.RawMessage `json:"filters,omitempty"` + Filters jsontext.Value `json:"filters,omitempty"` // CreatedBy holds the value of the "created_by" field. CreatedBy int64 `json:"created_by,omitempty"` // DeletedRows holds the value of the "deleted_rows" field. diff --git a/backend/ent/usagecleanuptask_create.go b/backend/ent/usagecleanuptask_create.go index 0b1dcff55e7..b6dbcb3cc81 100644 --- a/backend/ent/usagecleanuptask_create.go +++ b/backend/ent/usagecleanuptask_create.go @@ -4,7 +4,7 @@ package ent import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "time" @@ -58,7 +58,7 @@ func (_c *UsageCleanupTaskCreate) SetStatus(v string) *UsageCleanupTaskCreate { } // SetFilters sets the "filters" field. -func (_c *UsageCleanupTaskCreate) SetFilters(v json.RawMessage) *UsageCleanupTaskCreate { +func (_c *UsageCleanupTaskCreate) SetFilters(v jsontext.Value) *UsageCleanupTaskCreate { _c.mutation.SetFilters(v) return _c } @@ -375,7 +375,7 @@ func (u *UsageCleanupTaskUpsert) UpdateStatus() *UsageCleanupTaskUpsert { } // SetFilters sets the "filters" field. -func (u *UsageCleanupTaskUpsert) SetFilters(v json.RawMessage) *UsageCleanupTaskUpsert { +func (u *UsageCleanupTaskUpsert) SetFilters(v jsontext.Value) *UsageCleanupTaskUpsert { u.Set(usagecleanuptask.FieldFilters, v) return u } @@ -592,7 +592,7 @@ func (u *UsageCleanupTaskUpsertOne) UpdateStatus() *UsageCleanupTaskUpsertOne { } // SetFilters sets the "filters" field. -func (u *UsageCleanupTaskUpsertOne) SetFilters(v json.RawMessage) *UsageCleanupTaskUpsertOne { +func (u *UsageCleanupTaskUpsertOne) SetFilters(v jsontext.Value) *UsageCleanupTaskUpsertOne { return u.Update(func(s *UsageCleanupTaskUpsert) { s.SetFilters(v) }) @@ -999,7 +999,7 @@ func (u *UsageCleanupTaskUpsertBulk) UpdateStatus() *UsageCleanupTaskUpsertBulk } // SetFilters sets the "filters" field. -func (u *UsageCleanupTaskUpsertBulk) SetFilters(v json.RawMessage) *UsageCleanupTaskUpsertBulk { +func (u *UsageCleanupTaskUpsertBulk) SetFilters(v jsontext.Value) *UsageCleanupTaskUpsertBulk { return u.Update(func(s *UsageCleanupTaskUpsert) { s.SetFilters(v) }) diff --git a/backend/ent/usagecleanuptask_update.go b/backend/ent/usagecleanuptask_update.go index 604202c679f..d233258bbe7 100644 --- a/backend/ent/usagecleanuptask_update.go +++ b/backend/ent/usagecleanuptask_update.go @@ -4,7 +4,7 @@ package ent import ( "context" - "encoding/json" + "encoding/json/jsontext" "errors" "fmt" "time" @@ -51,13 +51,13 @@ func (_u *UsageCleanupTaskUpdate) SetNillableStatus(v *string) *UsageCleanupTask } // SetFilters sets the "filters" field. -func (_u *UsageCleanupTaskUpdate) SetFilters(v json.RawMessage) *UsageCleanupTaskUpdate { +func (_u *UsageCleanupTaskUpdate) SetFilters(v jsontext.Value) *UsageCleanupTaskUpdate { _u.mutation.SetFilters(v) return _u } // AppendFilters appends value to the "filters" field. -func (_u *UsageCleanupTaskUpdate) AppendFilters(v json.RawMessage) *UsageCleanupTaskUpdate { +func (_u *UsageCleanupTaskUpdate) AppendFilters(v jsontext.Value) *UsageCleanupTaskUpdate { _u.mutation.AppendFilters(v) return _u } @@ -374,13 +374,13 @@ func (_u *UsageCleanupTaskUpdateOne) SetNillableStatus(v *string) *UsageCleanupT } // SetFilters sets the "filters" field. -func (_u *UsageCleanupTaskUpdateOne) SetFilters(v json.RawMessage) *UsageCleanupTaskUpdateOne { +func (_u *UsageCleanupTaskUpdateOne) SetFilters(v jsontext.Value) *UsageCleanupTaskUpdateOne { _u.mutation.SetFilters(v) return _u } // AppendFilters appends value to the "filters" field. -func (_u *UsageCleanupTaskUpdateOne) AppendFilters(v json.RawMessage) *UsageCleanupTaskUpdateOne { +func (_u *UsageCleanupTaskUpdateOne) AppendFilters(v jsontext.Value) *UsageCleanupTaskUpdateOne { _u.mutation.AppendFilters(v) return _u } diff --git a/backend/ent/usersubscription.go b/backend/ent/usersubscription.go index 01beb2fcc9b..96ecc58f08c 100644 --- a/backend/ent/usersubscription.go +++ b/backend/ent/usersubscription.go @@ -69,9 +69,11 @@ type UserSubscriptionEdges struct { AssignedByUser *User `json:"assigned_by_user,omitempty"` // UsageLogs holds the value of the usage_logs edge. UsageLogs []*UsageLog `json:"usage_logs,omitempty"` + // QuotaEvents holds the value of the quota_events edge. + QuotaEvents []*UserSubscriptionQuotaEvent `json:"quota_events,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [4]bool + loadedTypes [5]bool } // UserOrErr returns the User value or an error if the edge @@ -116,6 +118,15 @@ func (e UserSubscriptionEdges) UsageLogsOrErr() ([]*UsageLog, error) { return nil, &NotLoadedError{edge: "usage_logs"} } +// QuotaEventsOrErr returns the QuotaEvents value or an error if the edge +// was not loaded in eager-loading. +func (e UserSubscriptionEdges) QuotaEventsOrErr() ([]*UserSubscriptionQuotaEvent, error) { + if e.loadedTypes[4] { + return e.QuotaEvents, nil + } + return nil, &NotLoadedError{edge: "quota_events"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*UserSubscription) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -291,6 +302,11 @@ func (_m *UserSubscription) QueryUsageLogs() *UsageLogQuery { return NewUserSubscriptionClient(_m.config).QueryUsageLogs(_m) } +// QueryQuotaEvents queries the "quota_events" edge of the UserSubscription entity. +func (_m *UserSubscription) QueryQuotaEvents() *UserSubscriptionQuotaEventQuery { + return NewUserSubscriptionClient(_m.config).QueryQuotaEvents(_m) +} + // Update returns a builder for updating this UserSubscription. // Note that you need to call UserSubscription.Unwrap() before calling this method if this UserSubscription // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/backend/ent/usersubscription/usersubscription.go b/backend/ent/usersubscription/usersubscription.go index 064416461f8..2a1d432d507 100644 --- a/backend/ent/usersubscription/usersubscription.go +++ b/backend/ent/usersubscription/usersubscription.go @@ -57,6 +57,8 @@ const ( EdgeAssignedByUser = "assigned_by_user" // EdgeUsageLogs holds the string denoting the usage_logs edge name in mutations. EdgeUsageLogs = "usage_logs" + // EdgeQuotaEvents holds the string denoting the quota_events edge name in mutations. + EdgeQuotaEvents = "quota_events" // Table holds the table name of the usersubscription in the database. Table = "user_subscriptions" // UserTable is the table that holds the user relation/edge. @@ -87,6 +89,13 @@ const ( UsageLogsInverseTable = "usage_logs" // UsageLogsColumn is the table column denoting the usage_logs relation/edge. UsageLogsColumn = "subscription_id" + // QuotaEventsTable is the table that holds the quota_events relation/edge. + QuotaEventsTable = "user_subscription_quota_events" + // QuotaEventsInverseTable is the table name for the UserSubscriptionQuotaEvent entity. + // It exists in this package in order to avoid circular dependency with the "usersubscriptionquotaevent" package. + QuotaEventsInverseTable = "user_subscription_quota_events" + // QuotaEventsColumn is the table column denoting the quota_events relation/edge. + QuotaEventsColumn = "user_subscription_id" ) // Columns holds all SQL columns for usersubscription fields. @@ -276,6 +285,20 @@ func ByUsageLogs(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { sqlgraph.OrderByNeighborTerms(s, newUsageLogsStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// ByQuotaEventsCount orders the results by quota_events count. +func ByQuotaEventsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newQuotaEventsStep(), opts...) + } +} + +// ByQuotaEvents orders the results by quota_events terms. +func ByQuotaEvents(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newQuotaEventsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newUserStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -304,3 +327,10 @@ func newUsageLogsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, UsageLogsTable, UsageLogsColumn), ) } +func newQuotaEventsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(QuotaEventsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, QuotaEventsTable, QuotaEventsColumn), + ) +} diff --git a/backend/ent/usersubscription/where.go b/backend/ent/usersubscription/where.go index 250e5ed5691..f2460b09748 100644 --- a/backend/ent/usersubscription/where.go +++ b/backend/ent/usersubscription/where.go @@ -962,6 +962,29 @@ func HasUsageLogsWith(preds ...predicate.UsageLog) predicate.UserSubscription { }) } +// HasQuotaEvents applies the HasEdge predicate on the "quota_events" edge. +func HasQuotaEvents() predicate.UserSubscription { + return predicate.UserSubscription(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, QuotaEventsTable, QuotaEventsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasQuotaEventsWith applies the HasEdge predicate on the "quota_events" edge with a given conditions (other predicates). +func HasQuotaEventsWith(preds ...predicate.UserSubscriptionQuotaEvent) predicate.UserSubscription { + return predicate.UserSubscription(func(s *sql.Selector) { + step := newQuotaEventsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.UserSubscription) predicate.UserSubscription { return predicate.UserSubscription(sql.AndPredicates(predicates...)) diff --git a/backend/ent/usersubscription_create.go b/backend/ent/usersubscription_create.go index dd03115bb20..4b292d73d46 100644 --- a/backend/ent/usersubscription_create.go +++ b/backend/ent/usersubscription_create.go @@ -15,6 +15,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" ) // UserSubscriptionCreate is the builder for creating a UserSubscription entity. @@ -275,6 +276,21 @@ func (_c *UserSubscriptionCreate) AddUsageLogs(v ...*UsageLog) *UserSubscription return _c.AddUsageLogIDs(ids...) } +// AddQuotaEventIDs adds the "quota_events" edge to the UserSubscriptionQuotaEvent entity by IDs. +func (_c *UserSubscriptionCreate) AddQuotaEventIDs(ids ...int64) *UserSubscriptionCreate { + _c.mutation.AddQuotaEventIDs(ids...) + return _c +} + +// AddQuotaEvents adds the "quota_events" edges to the UserSubscriptionQuotaEvent entity. +func (_c *UserSubscriptionCreate) AddQuotaEvents(v ...*UserSubscriptionQuotaEvent) *UserSubscriptionCreate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddQuotaEventIDs(ids...) +} + // Mutation returns the UserSubscriptionMutation object of the builder. func (_c *UserSubscriptionCreate) Mutation() *UserSubscriptionMutation { return _c.mutation @@ -548,6 +564,22 @@ func (_c *UserSubscriptionCreate) createSpec() (*UserSubscription, *sqlgraph.Cre } _spec.Edges = append(_spec.Edges, edge) } + if nodes := _c.mutation.QuotaEventsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/backend/ent/usersubscription_query.go b/backend/ent/usersubscription_query.go index 288b7b1d04f..656b0a436ae 100644 --- a/backend/ent/usersubscription_query.go +++ b/backend/ent/usersubscription_query.go @@ -18,6 +18,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" ) // UserSubscriptionQuery is the builder for querying UserSubscription entities. @@ -31,6 +32,7 @@ type UserSubscriptionQuery struct { withGroup *GroupQuery withAssignedByUser *UserQuery withUsageLogs *UsageLogQuery + withQuotaEvents *UserSubscriptionQuotaEventQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -156,6 +158,28 @@ func (_q *UserSubscriptionQuery) QueryUsageLogs() *UsageLogQuery { return query } +// QueryQuotaEvents chains the current query on the "quota_events" edge. +func (_q *UserSubscriptionQuery) QueryQuotaEvents() *UserSubscriptionQuotaEventQuery { + query := (&UserSubscriptionQuotaEventClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usersubscription.Table, usersubscription.FieldID, selector), + sqlgraph.To(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, usersubscription.QuotaEventsTable, usersubscription.QuotaEventsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first UserSubscription entity from the query. // Returns a *NotFoundError when no UserSubscription was found. func (_q *UserSubscriptionQuery) First(ctx context.Context) (*UserSubscription, error) { @@ -352,6 +376,7 @@ func (_q *UserSubscriptionQuery) Clone() *UserSubscriptionQuery { withGroup: _q.withGroup.Clone(), withAssignedByUser: _q.withAssignedByUser.Clone(), withUsageLogs: _q.withUsageLogs.Clone(), + withQuotaEvents: _q.withQuotaEvents.Clone(), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, @@ -402,6 +427,17 @@ func (_q *UserSubscriptionQuery) WithUsageLogs(opts ...func(*UsageLogQuery)) *Us return _q } +// WithQuotaEvents tells the query-builder to eager-load the nodes that are connected to +// the "quota_events" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *UserSubscriptionQuery) WithQuotaEvents(opts ...func(*UserSubscriptionQuotaEventQuery)) *UserSubscriptionQuery { + query := (&UserSubscriptionQuotaEventClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withQuotaEvents = query + return _q +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -480,11 +516,12 @@ func (_q *UserSubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) var ( nodes = []*UserSubscription{} _spec = _q.querySpec() - loadedTypes = [4]bool{ + loadedTypes = [5]bool{ _q.withUser != nil, _q.withGroup != nil, _q.withAssignedByUser != nil, _q.withUsageLogs != nil, + _q.withQuotaEvents != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -533,6 +570,15 @@ func (_q *UserSubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) return nil, err } } + if query := _q.withQuotaEvents; query != nil { + if err := _q.loadQuotaEvents(ctx, query, nodes, + func(n *UserSubscription) { n.Edges.QuotaEvents = []*UserSubscriptionQuotaEvent{} }, + func(n *UserSubscription, e *UserSubscriptionQuotaEvent) { + n.Edges.QuotaEvents = append(n.Edges.QuotaEvents, e) + }); err != nil { + return nil, err + } + } return nodes, nil } @@ -659,6 +705,36 @@ func (_q *UserSubscriptionQuery) loadUsageLogs(ctx context.Context, query *Usage } return nil } +func (_q *UserSubscriptionQuery) loadQuotaEvents(ctx context.Context, query *UserSubscriptionQuotaEventQuery, nodes []*UserSubscription, init func(*UserSubscription), assign func(*UserSubscription, *UserSubscriptionQuotaEvent)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[int64]*UserSubscription) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(usersubscriptionquotaevent.FieldUserSubscriptionID) + } + query.Where(predicate.UserSubscriptionQuotaEvent(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(usersubscription.QuotaEventsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.UserSubscriptionID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "user_subscription_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (_q *UserSubscriptionQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() diff --git a/backend/ent/usersubscription_update.go b/backend/ent/usersubscription_update.go index 811dae7ede5..4c21a669890 100644 --- a/backend/ent/usersubscription_update.go +++ b/backend/ent/usersubscription_update.go @@ -16,6 +16,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usagelog" "github.com/Wei-Shaw/sub2api/ent/user" "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" ) // UserSubscriptionUpdate is the builder for updating UserSubscription entities. @@ -348,6 +349,21 @@ func (_u *UserSubscriptionUpdate) AddUsageLogs(v ...*UsageLog) *UserSubscription return _u.AddUsageLogIDs(ids...) } +// AddQuotaEventIDs adds the "quota_events" edge to the UserSubscriptionQuotaEvent entity by IDs. +func (_u *UserSubscriptionUpdate) AddQuotaEventIDs(ids ...int64) *UserSubscriptionUpdate { + _u.mutation.AddQuotaEventIDs(ids...) + return _u +} + +// AddQuotaEvents adds the "quota_events" edges to the UserSubscriptionQuotaEvent entity. +func (_u *UserSubscriptionUpdate) AddQuotaEvents(v ...*UserSubscriptionQuotaEvent) *UserSubscriptionUpdate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddQuotaEventIDs(ids...) +} + // Mutation returns the UserSubscriptionMutation object of the builder. func (_u *UserSubscriptionUpdate) Mutation() *UserSubscriptionMutation { return _u.mutation @@ -392,6 +408,27 @@ func (_u *UserSubscriptionUpdate) RemoveUsageLogs(v ...*UsageLog) *UserSubscript return _u.RemoveUsageLogIDs(ids...) } +// ClearQuotaEvents clears all "quota_events" edges to the UserSubscriptionQuotaEvent entity. +func (_u *UserSubscriptionUpdate) ClearQuotaEvents() *UserSubscriptionUpdate { + _u.mutation.ClearQuotaEvents() + return _u +} + +// RemoveQuotaEventIDs removes the "quota_events" edge to UserSubscriptionQuotaEvent entities by IDs. +func (_u *UserSubscriptionUpdate) RemoveQuotaEventIDs(ids ...int64) *UserSubscriptionUpdate { + _u.mutation.RemoveQuotaEventIDs(ids...) + return _u +} + +// RemoveQuotaEvents removes "quota_events" edges to UserSubscriptionQuotaEvent entities. +func (_u *UserSubscriptionUpdate) RemoveQuotaEvents(v ...*UserSubscriptionQuotaEvent) *UserSubscriptionUpdate { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveQuotaEventIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (_u *UserSubscriptionUpdate) Save(ctx context.Context) (int, error) { if err := _u.defaults(); err != nil { @@ -657,6 +694,51 @@ func (_u *UserSubscriptionUpdate) sqlSave(ctx context.Context) (_node int, err e } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.QuotaEventsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedQuotaEventsIDs(); len(nodes) > 0 && !_u.mutation.QuotaEventsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.QuotaEventsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{usersubscription.Label} @@ -994,6 +1076,21 @@ func (_u *UserSubscriptionUpdateOne) AddUsageLogs(v ...*UsageLog) *UserSubscript return _u.AddUsageLogIDs(ids...) } +// AddQuotaEventIDs adds the "quota_events" edge to the UserSubscriptionQuotaEvent entity by IDs. +func (_u *UserSubscriptionUpdateOne) AddQuotaEventIDs(ids ...int64) *UserSubscriptionUpdateOne { + _u.mutation.AddQuotaEventIDs(ids...) + return _u +} + +// AddQuotaEvents adds the "quota_events" edges to the UserSubscriptionQuotaEvent entity. +func (_u *UserSubscriptionUpdateOne) AddQuotaEvents(v ...*UserSubscriptionQuotaEvent) *UserSubscriptionUpdateOne { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddQuotaEventIDs(ids...) +} + // Mutation returns the UserSubscriptionMutation object of the builder. func (_u *UserSubscriptionUpdateOne) Mutation() *UserSubscriptionMutation { return _u.mutation @@ -1038,6 +1135,27 @@ func (_u *UserSubscriptionUpdateOne) RemoveUsageLogs(v ...*UsageLog) *UserSubscr return _u.RemoveUsageLogIDs(ids...) } +// ClearQuotaEvents clears all "quota_events" edges to the UserSubscriptionQuotaEvent entity. +func (_u *UserSubscriptionUpdateOne) ClearQuotaEvents() *UserSubscriptionUpdateOne { + _u.mutation.ClearQuotaEvents() + return _u +} + +// RemoveQuotaEventIDs removes the "quota_events" edge to UserSubscriptionQuotaEvent entities by IDs. +func (_u *UserSubscriptionUpdateOne) RemoveQuotaEventIDs(ids ...int64) *UserSubscriptionUpdateOne { + _u.mutation.RemoveQuotaEventIDs(ids...) + return _u +} + +// RemoveQuotaEvents removes "quota_events" edges to UserSubscriptionQuotaEvent entities. +func (_u *UserSubscriptionUpdateOne) RemoveQuotaEvents(v ...*UserSubscriptionQuotaEvent) *UserSubscriptionUpdateOne { + ids := make([]int64, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveQuotaEventIDs(ids...) +} + // Where appends a list predicates to the UserSubscriptionUpdate builder. func (_u *UserSubscriptionUpdateOne) Where(ps ...predicate.UserSubscription) *UserSubscriptionUpdateOne { _u.mutation.Where(ps...) @@ -1333,6 +1451,51 @@ func (_u *UserSubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *UserSu } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.QuotaEventsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedQuotaEventsIDs(); len(nodes) > 0 && !_u.mutation.QuotaEventsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.QuotaEventsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: usersubscription.QuotaEventsTable, + Columns: []string{usersubscription.QuotaEventsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &UserSubscription{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/backend/ent/usersubscriptionquotaevent.go b/backend/ent/usersubscriptionquotaevent.go new file mode 100644 index 00000000000..e2fa826501d --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent.go @@ -0,0 +1,228 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" +) + +// UserSubscriptionQuotaEvent is the model entity for the UserSubscriptionQuotaEvent schema. +type UserSubscriptionQuotaEvent struct { + config `json:"-"` + // ID of the ent. + ID int64 `json:"id,omitempty"` + // UserSubscriptionID holds the value of the "user_subscription_id" field. + UserSubscriptionID int64 `json:"user_subscription_id,omitempty"` + // QuotaTotalUsd holds the value of the "quota_total_usd" field. + QuotaTotalUsd float64 `json:"quota_total_usd,omitempty"` + // QuotaUsedUsd holds the value of the "quota_used_usd" field. + QuotaUsedUsd float64 `json:"quota_used_usd,omitempty"` + // StartsAt holds the value of the "starts_at" field. + StartsAt time.Time `json:"starts_at,omitempty"` + // ExpiresAt holds the value of the "expires_at" field. + ExpiresAt time.Time `json:"expires_at,omitempty"` + // SourceKind holds the value of the "source_kind" field. + SourceKind string `json:"source_kind,omitempty"` + // SourceRef holds the value of the "source_ref" field. + SourceRef *string `json:"source_ref,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserSubscriptionQuotaEventQuery when eager-loading is set. + Edges UserSubscriptionQuotaEventEdges `json:"edges"` + selectValues sql.SelectValues +} + +// UserSubscriptionQuotaEventEdges holds the relations/edges for other nodes in the graph. +type UserSubscriptionQuotaEventEdges struct { + // Subscription holds the value of the subscription edge. + Subscription *UserSubscription `json:"subscription,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// SubscriptionOrErr returns the Subscription value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e UserSubscriptionQuotaEventEdges) SubscriptionOrErr() (*UserSubscription, error) { + if e.Subscription != nil { + return e.Subscription, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: usersubscription.Label} + } + return nil, &NotLoadedError{edge: "subscription"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*UserSubscriptionQuotaEvent) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case usersubscriptionquotaevent.FieldQuotaTotalUsd, usersubscriptionquotaevent.FieldQuotaUsedUsd: + values[i] = new(sql.NullFloat64) + case usersubscriptionquotaevent.FieldID, usersubscriptionquotaevent.FieldUserSubscriptionID: + values[i] = new(sql.NullInt64) + case usersubscriptionquotaevent.FieldSourceKind, usersubscriptionquotaevent.FieldSourceRef: + values[i] = new(sql.NullString) + case usersubscriptionquotaevent.FieldStartsAt, usersubscriptionquotaevent.FieldExpiresAt, usersubscriptionquotaevent.FieldCreatedAt, usersubscriptionquotaevent.FieldUpdatedAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the UserSubscriptionQuotaEvent fields. +func (_m *UserSubscriptionQuotaEvent) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case usersubscriptionquotaevent.FieldID: + value, ok := values[i].(*sql.NullInt64) + if !ok { + return fmt.Errorf("unexpected type %T for field id", value) + } + _m.ID = int64(value.Int64) + case usersubscriptionquotaevent.FieldUserSubscriptionID: + if value, ok := values[i].(*sql.NullInt64); !ok { + return fmt.Errorf("unexpected type %T for field user_subscription_id", values[i]) + } else if value.Valid { + _m.UserSubscriptionID = value.Int64 + } + case usersubscriptionquotaevent.FieldQuotaTotalUsd: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field quota_total_usd", values[i]) + } else if value.Valid { + _m.QuotaTotalUsd = value.Float64 + } + case usersubscriptionquotaevent.FieldQuotaUsedUsd: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field quota_used_usd", values[i]) + } else if value.Valid { + _m.QuotaUsedUsd = value.Float64 + } + case usersubscriptionquotaevent.FieldStartsAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field starts_at", values[i]) + } else if value.Valid { + _m.StartsAt = value.Time + } + case usersubscriptionquotaevent.FieldExpiresAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field expires_at", values[i]) + } else if value.Valid { + _m.ExpiresAt = value.Time + } + case usersubscriptionquotaevent.FieldSourceKind: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source_kind", values[i]) + } else if value.Valid { + _m.SourceKind = value.String + } + case usersubscriptionquotaevent.FieldSourceRef: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field source_ref", values[i]) + } else if value.Valid { + _m.SourceRef = new(string) + *_m.SourceRef = value.String + } + case usersubscriptionquotaevent.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case usersubscriptionquotaevent.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the UserSubscriptionQuotaEvent. +// This includes values selected through modifiers, order, etc. +func (_m *UserSubscriptionQuotaEvent) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QuerySubscription queries the "subscription" edge of the UserSubscriptionQuotaEvent entity. +func (_m *UserSubscriptionQuotaEvent) QuerySubscription() *UserSubscriptionQuery { + return NewUserSubscriptionQuotaEventClient(_m.config).QuerySubscription(_m) +} + +// Update returns a builder for updating this UserSubscriptionQuotaEvent. +// Note that you need to call UserSubscriptionQuotaEvent.Unwrap() before calling this method if this UserSubscriptionQuotaEvent +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *UserSubscriptionQuotaEvent) Update() *UserSubscriptionQuotaEventUpdateOne { + return NewUserSubscriptionQuotaEventClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the UserSubscriptionQuotaEvent entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *UserSubscriptionQuotaEvent) Unwrap() *UserSubscriptionQuotaEvent { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("ent: UserSubscriptionQuotaEvent is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *UserSubscriptionQuotaEvent) String() string { + var builder strings.Builder + builder.WriteString("UserSubscriptionQuotaEvent(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("user_subscription_id=") + builder.WriteString(fmt.Sprintf("%v", _m.UserSubscriptionID)) + builder.WriteString(", ") + builder.WriteString("quota_total_usd=") + builder.WriteString(fmt.Sprintf("%v", _m.QuotaTotalUsd)) + builder.WriteString(", ") + builder.WriteString("quota_used_usd=") + builder.WriteString(fmt.Sprintf("%v", _m.QuotaUsedUsd)) + builder.WriteString(", ") + builder.WriteString("starts_at=") + builder.WriteString(_m.StartsAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("expires_at=") + builder.WriteString(_m.ExpiresAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("source_kind=") + builder.WriteString(_m.SourceKind) + builder.WriteString(", ") + if v := _m.SourceRef; v != nil { + builder.WriteString("source_ref=") + builder.WriteString(*v) + } + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteByte(')') + return builder.String() +} + +// UserSubscriptionQuotaEvents is a parsable slice of UserSubscriptionQuotaEvent. +type UserSubscriptionQuotaEvents []*UserSubscriptionQuotaEvent diff --git a/backend/ent/usersubscriptionquotaevent/usersubscriptionquotaevent.go b/backend/ent/usersubscriptionquotaevent/usersubscriptionquotaevent.go new file mode 100644 index 00000000000..0ddc5084b7b --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent/usersubscriptionquotaevent.go @@ -0,0 +1,150 @@ +// Code generated by ent, DO NOT EDIT. + +package usersubscriptionquotaevent + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the usersubscriptionquotaevent type in the database. + Label = "user_subscription_quota_event" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldUserSubscriptionID holds the string denoting the user_subscription_id field in the database. + FieldUserSubscriptionID = "user_subscription_id" + // FieldQuotaTotalUsd holds the string denoting the quota_total_usd field in the database. + FieldQuotaTotalUsd = "quota_total_usd" + // FieldQuotaUsedUsd holds the string denoting the quota_used_usd field in the database. + FieldQuotaUsedUsd = "quota_used_usd" + // FieldStartsAt holds the string denoting the starts_at field in the database. + FieldStartsAt = "starts_at" + // FieldExpiresAt holds the string denoting the expires_at field in the database. + FieldExpiresAt = "expires_at" + // FieldSourceKind holds the string denoting the source_kind field in the database. + FieldSourceKind = "source_kind" + // FieldSourceRef holds the string denoting the source_ref field in the database. + FieldSourceRef = "source_ref" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // EdgeSubscription holds the string denoting the subscription edge name in mutations. + EdgeSubscription = "subscription" + // Table holds the table name of the usersubscriptionquotaevent in the database. + Table = "user_subscription_quota_events" + // SubscriptionTable is the table that holds the subscription relation/edge. + SubscriptionTable = "user_subscription_quota_events" + // SubscriptionInverseTable is the table name for the UserSubscription entity. + // It exists in this package in order to avoid circular dependency with the "usersubscription" package. + SubscriptionInverseTable = "user_subscriptions" + // SubscriptionColumn is the table column denoting the subscription relation/edge. + SubscriptionColumn = "user_subscription_id" +) + +// Columns holds all SQL columns for usersubscriptionquotaevent fields. +var Columns = []string{ + FieldID, + FieldUserSubscriptionID, + FieldQuotaTotalUsd, + FieldQuotaUsedUsd, + FieldStartsAt, + FieldExpiresAt, + FieldSourceKind, + FieldSourceRef, + FieldCreatedAt, + FieldUpdatedAt, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // DefaultQuotaUsedUsd holds the default value on creation for the "quota_used_usd" field. + DefaultQuotaUsedUsd float64 + // SourceKindValidator is a validator for the "source_kind" field. It is called by the builders before save. + SourceKindValidator func(string) error + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time +) + +// OrderOption defines the ordering options for the UserSubscriptionQuotaEvent queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByUserSubscriptionID orders the results by the user_subscription_id field. +func ByUserSubscriptionID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUserSubscriptionID, opts...).ToFunc() +} + +// ByQuotaTotalUsd orders the results by the quota_total_usd field. +func ByQuotaTotalUsd(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldQuotaTotalUsd, opts...).ToFunc() +} + +// ByQuotaUsedUsd orders the results by the quota_used_usd field. +func ByQuotaUsedUsd(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldQuotaUsedUsd, opts...).ToFunc() +} + +// ByStartsAt orders the results by the starts_at field. +func ByStartsAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldStartsAt, opts...).ToFunc() +} + +// ByExpiresAt orders the results by the expires_at field. +func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() +} + +// BySourceKind orders the results by the source_kind field. +func BySourceKind(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceKind, opts...).ToFunc() +} + +// BySourceRef orders the results by the source_ref field. +func BySourceRef(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSourceRef, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// BySubscriptionField orders the results by subscription field. +func BySubscriptionField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSubscriptionStep(), sql.OrderByField(field, opts...)) + } +} +func newSubscriptionStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SubscriptionInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, SubscriptionTable, SubscriptionColumn), + ) +} diff --git a/backend/ent/usersubscriptionquotaevent/where.go b/backend/ent/usersubscriptionquotaevent/where.go new file mode 100644 index 00000000000..c04bdfeccb1 --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent/where.go @@ -0,0 +1,539 @@ +// Code generated by ent, DO NOT EDIT. + +package usersubscriptionquotaevent + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/Wei-Shaw/sub2api/ent/predicate" +) + +// ID filters vertices based on their ID field. +func ID(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldID, id)) +} + +// UserSubscriptionID applies equality check predicate on the "user_subscription_id" field. It's identical to UserSubscriptionIDEQ. +func UserSubscriptionID(v int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldUserSubscriptionID, v)) +} + +// QuotaTotalUsd applies equality check predicate on the "quota_total_usd" field. It's identical to QuotaTotalUsdEQ. +func QuotaTotalUsd(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldQuotaTotalUsd, v)) +} + +// QuotaUsedUsd applies equality check predicate on the "quota_used_usd" field. It's identical to QuotaUsedUsdEQ. +func QuotaUsedUsd(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldQuotaUsedUsd, v)) +} + +// StartsAt applies equality check predicate on the "starts_at" field. It's identical to StartsAtEQ. +func StartsAt(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldStartsAt, v)) +} + +// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ. +func ExpiresAt(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldExpiresAt, v)) +} + +// SourceKind applies equality check predicate on the "source_kind" field. It's identical to SourceKindEQ. +func SourceKind(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldSourceKind, v)) +} + +// SourceRef applies equality check predicate on the "source_ref" field. It's identical to SourceRefEQ. +func SourceRef(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldSourceRef, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UserSubscriptionIDEQ applies the EQ predicate on the "user_subscription_id" field. +func UserSubscriptionIDEQ(v int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldUserSubscriptionID, v)) +} + +// UserSubscriptionIDNEQ applies the NEQ predicate on the "user_subscription_id" field. +func UserSubscriptionIDNEQ(v int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldUserSubscriptionID, v)) +} + +// UserSubscriptionIDIn applies the In predicate on the "user_subscription_id" field. +func UserSubscriptionIDIn(vs ...int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldUserSubscriptionID, vs...)) +} + +// UserSubscriptionIDNotIn applies the NotIn predicate on the "user_subscription_id" field. +func UserSubscriptionIDNotIn(vs ...int64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldUserSubscriptionID, vs...)) +} + +// QuotaTotalUsdEQ applies the EQ predicate on the "quota_total_usd" field. +func QuotaTotalUsdEQ(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldQuotaTotalUsd, v)) +} + +// QuotaTotalUsdNEQ applies the NEQ predicate on the "quota_total_usd" field. +func QuotaTotalUsdNEQ(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldQuotaTotalUsd, v)) +} + +// QuotaTotalUsdIn applies the In predicate on the "quota_total_usd" field. +func QuotaTotalUsdIn(vs ...float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldQuotaTotalUsd, vs...)) +} + +// QuotaTotalUsdNotIn applies the NotIn predicate on the "quota_total_usd" field. +func QuotaTotalUsdNotIn(vs ...float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldQuotaTotalUsd, vs...)) +} + +// QuotaTotalUsdGT applies the GT predicate on the "quota_total_usd" field. +func QuotaTotalUsdGT(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldQuotaTotalUsd, v)) +} + +// QuotaTotalUsdGTE applies the GTE predicate on the "quota_total_usd" field. +func QuotaTotalUsdGTE(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldQuotaTotalUsd, v)) +} + +// QuotaTotalUsdLT applies the LT predicate on the "quota_total_usd" field. +func QuotaTotalUsdLT(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldQuotaTotalUsd, v)) +} + +// QuotaTotalUsdLTE applies the LTE predicate on the "quota_total_usd" field. +func QuotaTotalUsdLTE(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldQuotaTotalUsd, v)) +} + +// QuotaUsedUsdEQ applies the EQ predicate on the "quota_used_usd" field. +func QuotaUsedUsdEQ(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldQuotaUsedUsd, v)) +} + +// QuotaUsedUsdNEQ applies the NEQ predicate on the "quota_used_usd" field. +func QuotaUsedUsdNEQ(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldQuotaUsedUsd, v)) +} + +// QuotaUsedUsdIn applies the In predicate on the "quota_used_usd" field. +func QuotaUsedUsdIn(vs ...float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldQuotaUsedUsd, vs...)) +} + +// QuotaUsedUsdNotIn applies the NotIn predicate on the "quota_used_usd" field. +func QuotaUsedUsdNotIn(vs ...float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldQuotaUsedUsd, vs...)) +} + +// QuotaUsedUsdGT applies the GT predicate on the "quota_used_usd" field. +func QuotaUsedUsdGT(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldQuotaUsedUsd, v)) +} + +// QuotaUsedUsdGTE applies the GTE predicate on the "quota_used_usd" field. +func QuotaUsedUsdGTE(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldQuotaUsedUsd, v)) +} + +// QuotaUsedUsdLT applies the LT predicate on the "quota_used_usd" field. +func QuotaUsedUsdLT(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldQuotaUsedUsd, v)) +} + +// QuotaUsedUsdLTE applies the LTE predicate on the "quota_used_usd" field. +func QuotaUsedUsdLTE(v float64) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldQuotaUsedUsd, v)) +} + +// StartsAtEQ applies the EQ predicate on the "starts_at" field. +func StartsAtEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldStartsAt, v)) +} + +// StartsAtNEQ applies the NEQ predicate on the "starts_at" field. +func StartsAtNEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldStartsAt, v)) +} + +// StartsAtIn applies the In predicate on the "starts_at" field. +func StartsAtIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldStartsAt, vs...)) +} + +// StartsAtNotIn applies the NotIn predicate on the "starts_at" field. +func StartsAtNotIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldStartsAt, vs...)) +} + +// StartsAtGT applies the GT predicate on the "starts_at" field. +func StartsAtGT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldStartsAt, v)) +} + +// StartsAtGTE applies the GTE predicate on the "starts_at" field. +func StartsAtGTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldStartsAt, v)) +} + +// StartsAtLT applies the LT predicate on the "starts_at" field. +func StartsAtLT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldStartsAt, v)) +} + +// StartsAtLTE applies the LTE predicate on the "starts_at" field. +func StartsAtLTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldStartsAt, v)) +} + +// ExpiresAtEQ applies the EQ predicate on the "expires_at" field. +func ExpiresAtEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldExpiresAt, v)) +} + +// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field. +func ExpiresAtNEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldExpiresAt, v)) +} + +// ExpiresAtIn applies the In predicate on the "expires_at" field. +func ExpiresAtIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field. +func ExpiresAtNotIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldExpiresAt, vs...)) +} + +// ExpiresAtGT applies the GT predicate on the "expires_at" field. +func ExpiresAtGT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldExpiresAt, v)) +} + +// ExpiresAtGTE applies the GTE predicate on the "expires_at" field. +func ExpiresAtGTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldExpiresAt, v)) +} + +// ExpiresAtLT applies the LT predicate on the "expires_at" field. +func ExpiresAtLT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldExpiresAt, v)) +} + +// ExpiresAtLTE applies the LTE predicate on the "expires_at" field. +func ExpiresAtLTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldExpiresAt, v)) +} + +// SourceKindEQ applies the EQ predicate on the "source_kind" field. +func SourceKindEQ(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldSourceKind, v)) +} + +// SourceKindNEQ applies the NEQ predicate on the "source_kind" field. +func SourceKindNEQ(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldSourceKind, v)) +} + +// SourceKindIn applies the In predicate on the "source_kind" field. +func SourceKindIn(vs ...string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldSourceKind, vs...)) +} + +// SourceKindNotIn applies the NotIn predicate on the "source_kind" field. +func SourceKindNotIn(vs ...string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldSourceKind, vs...)) +} + +// SourceKindGT applies the GT predicate on the "source_kind" field. +func SourceKindGT(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldSourceKind, v)) +} + +// SourceKindGTE applies the GTE predicate on the "source_kind" field. +func SourceKindGTE(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldSourceKind, v)) +} + +// SourceKindLT applies the LT predicate on the "source_kind" field. +func SourceKindLT(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldSourceKind, v)) +} + +// SourceKindLTE applies the LTE predicate on the "source_kind" field. +func SourceKindLTE(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldSourceKind, v)) +} + +// SourceKindContains applies the Contains predicate on the "source_kind" field. +func SourceKindContains(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldContains(FieldSourceKind, v)) +} + +// SourceKindHasPrefix applies the HasPrefix predicate on the "source_kind" field. +func SourceKindHasPrefix(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldHasPrefix(FieldSourceKind, v)) +} + +// SourceKindHasSuffix applies the HasSuffix predicate on the "source_kind" field. +func SourceKindHasSuffix(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldHasSuffix(FieldSourceKind, v)) +} + +// SourceKindEqualFold applies the EqualFold predicate on the "source_kind" field. +func SourceKindEqualFold(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEqualFold(FieldSourceKind, v)) +} + +// SourceKindContainsFold applies the ContainsFold predicate on the "source_kind" field. +func SourceKindContainsFold(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldContainsFold(FieldSourceKind, v)) +} + +// SourceRefEQ applies the EQ predicate on the "source_ref" field. +func SourceRefEQ(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldSourceRef, v)) +} + +// SourceRefNEQ applies the NEQ predicate on the "source_ref" field. +func SourceRefNEQ(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldSourceRef, v)) +} + +// SourceRefIn applies the In predicate on the "source_ref" field. +func SourceRefIn(vs ...string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldSourceRef, vs...)) +} + +// SourceRefNotIn applies the NotIn predicate on the "source_ref" field. +func SourceRefNotIn(vs ...string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldSourceRef, vs...)) +} + +// SourceRefGT applies the GT predicate on the "source_ref" field. +func SourceRefGT(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldSourceRef, v)) +} + +// SourceRefGTE applies the GTE predicate on the "source_ref" field. +func SourceRefGTE(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldSourceRef, v)) +} + +// SourceRefLT applies the LT predicate on the "source_ref" field. +func SourceRefLT(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldSourceRef, v)) +} + +// SourceRefLTE applies the LTE predicate on the "source_ref" field. +func SourceRefLTE(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldSourceRef, v)) +} + +// SourceRefContains applies the Contains predicate on the "source_ref" field. +func SourceRefContains(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldContains(FieldSourceRef, v)) +} + +// SourceRefHasPrefix applies the HasPrefix predicate on the "source_ref" field. +func SourceRefHasPrefix(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldHasPrefix(FieldSourceRef, v)) +} + +// SourceRefHasSuffix applies the HasSuffix predicate on the "source_ref" field. +func SourceRefHasSuffix(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldHasSuffix(FieldSourceRef, v)) +} + +// SourceRefIsNil applies the IsNil predicate on the "source_ref" field. +func SourceRefIsNil() predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIsNull(FieldSourceRef)) +} + +// SourceRefNotNil applies the NotNil predicate on the "source_ref" field. +func SourceRefNotNil() predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotNull(FieldSourceRef)) +} + +// SourceRefEqualFold applies the EqualFold predicate on the "source_ref" field. +func SourceRefEqualFold(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEqualFold(FieldSourceRef, v)) +} + +// SourceRefContainsFold applies the ContainsFold predicate on the "source_ref" field. +func SourceRefContainsFold(v string) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldContainsFold(FieldSourceRef, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// HasSubscription applies the HasEdge predicate on the "subscription" edge. +func HasSubscription() predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, SubscriptionTable, SubscriptionColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSubscriptionWith applies the HasEdge predicate on the "subscription" edge with a given conditions (other predicates). +func HasSubscriptionWith(preds ...predicate.UserSubscription) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(func(s *sql.Selector) { + step := newSubscriptionStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.UserSubscriptionQuotaEvent) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.UserSubscriptionQuotaEvent) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.UserSubscriptionQuotaEvent) predicate.UserSubscriptionQuotaEvent { + return predicate.UserSubscriptionQuotaEvent(sql.NotPredicates(p)) +} diff --git a/backend/ent/usersubscriptionquotaevent_create.go b/backend/ent/usersubscriptionquotaevent_create.go new file mode 100644 index 00000000000..fe66fad6598 --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent_create.go @@ -0,0 +1,991 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" +) + +// UserSubscriptionQuotaEventCreate is the builder for creating a UserSubscriptionQuotaEvent entity. +type UserSubscriptionQuotaEventCreate struct { + config + mutation *UserSubscriptionQuotaEventMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (_c *UserSubscriptionQuotaEventCreate) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetUserSubscriptionID(v) + return _c +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (_c *UserSubscriptionQuotaEventCreate) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetQuotaTotalUsd(v) + return _c +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (_c *UserSubscriptionQuotaEventCreate) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetQuotaUsedUsd(v) + return _c +} + +// SetNillableQuotaUsedUsd sets the "quota_used_usd" field if the given value is not nil. +func (_c *UserSubscriptionQuotaEventCreate) SetNillableQuotaUsedUsd(v *float64) *UserSubscriptionQuotaEventCreate { + if v != nil { + _c.SetQuotaUsedUsd(*v) + } + return _c +} + +// SetStartsAt sets the "starts_at" field. +func (_c *UserSubscriptionQuotaEventCreate) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetStartsAt(v) + return _c +} + +// SetExpiresAt sets the "expires_at" field. +func (_c *UserSubscriptionQuotaEventCreate) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetExpiresAt(v) + return _c +} + +// SetSourceKind sets the "source_kind" field. +func (_c *UserSubscriptionQuotaEventCreate) SetSourceKind(v string) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetSourceKind(v) + return _c +} + +// SetSourceRef sets the "source_ref" field. +func (_c *UserSubscriptionQuotaEventCreate) SetSourceRef(v string) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetSourceRef(v) + return _c +} + +// SetNillableSourceRef sets the "source_ref" field if the given value is not nil. +func (_c *UserSubscriptionQuotaEventCreate) SetNillableSourceRef(v *string) *UserSubscriptionQuotaEventCreate { + if v != nil { + _c.SetSourceRef(*v) + } + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *UserSubscriptionQuotaEventCreate) SetCreatedAt(v time.Time) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *UserSubscriptionQuotaEventCreate) SetNillableCreatedAt(v *time.Time) *UserSubscriptionQuotaEventCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *UserSubscriptionQuotaEventCreate) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *UserSubscriptionQuotaEventCreate) SetNillableUpdatedAt(v *time.Time) *UserSubscriptionQuotaEventCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetSubscriptionID sets the "subscription" edge to the UserSubscription entity by ID. +func (_c *UserSubscriptionQuotaEventCreate) SetSubscriptionID(id int64) *UserSubscriptionQuotaEventCreate { + _c.mutation.SetSubscriptionID(id) + return _c +} + +// SetSubscription sets the "subscription" edge to the UserSubscription entity. +func (_c *UserSubscriptionQuotaEventCreate) SetSubscription(v *UserSubscription) *UserSubscriptionQuotaEventCreate { + return _c.SetSubscriptionID(v.ID) +} + +// Mutation returns the UserSubscriptionQuotaEventMutation object of the builder. +func (_c *UserSubscriptionQuotaEventCreate) Mutation() *UserSubscriptionQuotaEventMutation { + return _c.mutation +} + +// Save creates the UserSubscriptionQuotaEvent in the database. +func (_c *UserSubscriptionQuotaEventCreate) Save(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *UserSubscriptionQuotaEventCreate) SaveX(ctx context.Context) *UserSubscriptionQuotaEvent { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserSubscriptionQuotaEventCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserSubscriptionQuotaEventCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *UserSubscriptionQuotaEventCreate) defaults() { + if _, ok := _c.mutation.QuotaUsedUsd(); !ok { + v := usersubscriptionquotaevent.DefaultQuotaUsedUsd + _c.mutation.SetQuotaUsedUsd(v) + } + if _, ok := _c.mutation.CreatedAt(); !ok { + v := usersubscriptionquotaevent.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := usersubscriptionquotaevent.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *UserSubscriptionQuotaEventCreate) check() error { + if _, ok := _c.mutation.UserSubscriptionID(); !ok { + return &ValidationError{Name: "user_subscription_id", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.user_subscription_id"`)} + } + if _, ok := _c.mutation.QuotaTotalUsd(); !ok { + return &ValidationError{Name: "quota_total_usd", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.quota_total_usd"`)} + } + if _, ok := _c.mutation.QuotaUsedUsd(); !ok { + return &ValidationError{Name: "quota_used_usd", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.quota_used_usd"`)} + } + if _, ok := _c.mutation.StartsAt(); !ok { + return &ValidationError{Name: "starts_at", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.starts_at"`)} + } + if _, ok := _c.mutation.ExpiresAt(); !ok { + return &ValidationError{Name: "expires_at", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.expires_at"`)} + } + if _, ok := _c.mutation.SourceKind(); !ok { + return &ValidationError{Name: "source_kind", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.source_kind"`)} + } + if v, ok := _c.mutation.SourceKind(); ok { + if err := usersubscriptionquotaevent.SourceKindValidator(v); err != nil { + return &ValidationError{Name: "source_kind", err: fmt.Errorf(`ent: validator failed for field "UserSubscriptionQuotaEvent.source_kind": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "UserSubscriptionQuotaEvent.updated_at"`)} + } + if len(_c.mutation.SubscriptionIDs()) == 0 { + return &ValidationError{Name: "subscription", err: errors.New(`ent: missing required edge "UserSubscriptionQuotaEvent.subscription"`)} + } + return nil +} + +func (_c *UserSubscriptionQuotaEventCreate) sqlSave(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + id := _spec.ID.Value.(int64) + _node.ID = int64(id) + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *UserSubscriptionQuotaEventCreate) createSpec() (*UserSubscriptionQuotaEvent, *sqlgraph.CreateSpec) { + var ( + _node = &UserSubscriptionQuotaEvent{config: _c.config} + _spec = sqlgraph.NewCreateSpec(usersubscriptionquotaevent.Table, sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64)) + ) + _spec.OnConflict = _c.conflict + if value, ok := _c.mutation.QuotaTotalUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaTotalUsd, field.TypeFloat64, value) + _node.QuotaTotalUsd = value + } + if value, ok := _c.mutation.QuotaUsedUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaUsedUsd, field.TypeFloat64, value) + _node.QuotaUsedUsd = value + } + if value, ok := _c.mutation.StartsAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldStartsAt, field.TypeTime, value) + _node.StartsAt = value + } + if value, ok := _c.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldExpiresAt, field.TypeTime, value) + _node.ExpiresAt = value + } + if value, ok := _c.mutation.SourceKind(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceKind, field.TypeString, value) + _node.SourceKind = value + } + if value, ok := _c.mutation.SourceRef(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceRef, field.TypeString, value) + _node.SourceRef = &value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if nodes := _c.mutation.SubscriptionIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscriptionquotaevent.SubscriptionTable, + Columns: []string{usersubscriptionquotaevent.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.UserSubscriptionID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.UserSubscriptionQuotaEvent.Create(). +// SetUserSubscriptionID(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.UserSubscriptionQuotaEventUpsert) { +// SetUserSubscriptionID(v+v). +// }). +// Exec(ctx) +func (_c *UserSubscriptionQuotaEventCreate) OnConflict(opts ...sql.ConflictOption) *UserSubscriptionQuotaEventUpsertOne { + _c.conflict = opts + return &UserSubscriptionQuotaEventUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *UserSubscriptionQuotaEventCreate) OnConflictColumns(columns ...string) *UserSubscriptionQuotaEventUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &UserSubscriptionQuotaEventUpsertOne{ + create: _c, + } +} + +type ( + // UserSubscriptionQuotaEventUpsertOne is the builder for "upsert"-ing + // one UserSubscriptionQuotaEvent node. + UserSubscriptionQuotaEventUpsertOne struct { + create *UserSubscriptionQuotaEventCreate + } + + // UserSubscriptionQuotaEventUpsert is the "OnConflict" setter. + UserSubscriptionQuotaEventUpsert struct { + *sql.UpdateSet + } +) + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (u *UserSubscriptionQuotaEventUpsert) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldUserSubscriptionID, v) + return u +} + +// UpdateUserSubscriptionID sets the "user_subscription_id" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateUserSubscriptionID() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldUserSubscriptionID) + return u +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsert) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldQuotaTotalUsd, v) + return u +} + +// UpdateQuotaTotalUsd sets the "quota_total_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateQuotaTotalUsd() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldQuotaTotalUsd) + return u +} + +// AddQuotaTotalUsd adds v to the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsert) AddQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsert { + u.Add(usersubscriptionquotaevent.FieldQuotaTotalUsd, v) + return u +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsert) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldQuotaUsedUsd, v) + return u +} + +// UpdateQuotaUsedUsd sets the "quota_used_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateQuotaUsedUsd() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldQuotaUsedUsd) + return u +} + +// AddQuotaUsedUsd adds v to the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsert) AddQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsert { + u.Add(usersubscriptionquotaevent.FieldQuotaUsedUsd, v) + return u +} + +// SetStartsAt sets the "starts_at" field. +func (u *UserSubscriptionQuotaEventUpsert) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldStartsAt, v) + return u +} + +// UpdateStartsAt sets the "starts_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateStartsAt() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldStartsAt) + return u +} + +// SetExpiresAt sets the "expires_at" field. +func (u *UserSubscriptionQuotaEventUpsert) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldExpiresAt, v) + return u +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateExpiresAt() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldExpiresAt) + return u +} + +// SetSourceKind sets the "source_kind" field. +func (u *UserSubscriptionQuotaEventUpsert) SetSourceKind(v string) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldSourceKind, v) + return u +} + +// UpdateSourceKind sets the "source_kind" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateSourceKind() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldSourceKind) + return u +} + +// SetSourceRef sets the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsert) SetSourceRef(v string) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldSourceRef, v) + return u +} + +// UpdateSourceRef sets the "source_ref" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateSourceRef() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldSourceRef) + return u +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsert) ClearSourceRef() *UserSubscriptionQuotaEventUpsert { + u.SetNull(usersubscriptionquotaevent.FieldSourceRef) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *UserSubscriptionQuotaEventUpsert) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventUpsert { + u.Set(usersubscriptionquotaevent.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsert) UpdateUpdatedAt() *UserSubscriptionQuotaEventUpsert { + u.SetExcluded(usersubscriptionquotaevent.FieldUpdatedAt) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create. +// Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateNewValues() *UserSubscriptionQuotaEventUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.CreatedAt(); exists { + s.SetIgnore(usersubscriptionquotaevent.FieldCreatedAt) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *UserSubscriptionQuotaEventUpsertOne) Ignore() *UserSubscriptionQuotaEventUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *UserSubscriptionQuotaEventUpsertOne) DoNothing() *UserSubscriptionQuotaEventUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the UserSubscriptionQuotaEventCreate.OnConflict +// documentation for more info. +func (u *UserSubscriptionQuotaEventUpsertOne) Update(set func(*UserSubscriptionQuotaEventUpsert)) *UserSubscriptionQuotaEventUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&UserSubscriptionQuotaEventUpsert{UpdateSet: update}) + })) + return u +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetUserSubscriptionID(v) + }) +} + +// UpdateUserSubscriptionID sets the "user_subscription_id" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateUserSubscriptionID() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateUserSubscriptionID() + }) +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetQuotaTotalUsd(v) + }) +} + +// AddQuotaTotalUsd adds v to the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsertOne) AddQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.AddQuotaTotalUsd(v) + }) +} + +// UpdateQuotaTotalUsd sets the "quota_total_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateQuotaTotalUsd() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateQuotaTotalUsd() + }) +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetQuotaUsedUsd(v) + }) +} + +// AddQuotaUsedUsd adds v to the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsertOne) AddQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.AddQuotaUsedUsd(v) + }) +} + +// UpdateQuotaUsedUsd sets the "quota_used_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateQuotaUsedUsd() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateQuotaUsedUsd() + }) +} + +// SetStartsAt sets the "starts_at" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetStartsAt(v) + }) +} + +// UpdateStartsAt sets the "starts_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateStartsAt() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateStartsAt() + }) +} + +// SetExpiresAt sets the "expires_at" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetExpiresAt(v) + }) +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateExpiresAt() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateExpiresAt() + }) +} + +// SetSourceKind sets the "source_kind" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetSourceKind(v string) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetSourceKind(v) + }) +} + +// UpdateSourceKind sets the "source_kind" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateSourceKind() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateSourceKind() + }) +} + +// SetSourceRef sets the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetSourceRef(v string) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetSourceRef(v) + }) +} + +// UpdateSourceRef sets the "source_ref" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateSourceRef() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateSourceRef() + }) +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsertOne) ClearSourceRef() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.ClearSourceRef() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *UserSubscriptionQuotaEventUpsertOne) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertOne) UpdateUpdatedAt() *UserSubscriptionQuotaEventUpsertOne { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *UserSubscriptionQuotaEventUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for UserSubscriptionQuotaEventCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *UserSubscriptionQuotaEventUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *UserSubscriptionQuotaEventUpsertOne) ID(ctx context.Context) (id int64, err error) { + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *UserSubscriptionQuotaEventUpsertOne) IDX(ctx context.Context) int64 { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// UserSubscriptionQuotaEventCreateBulk is the builder for creating many UserSubscriptionQuotaEvent entities in bulk. +type UserSubscriptionQuotaEventCreateBulk struct { + config + err error + builders []*UserSubscriptionQuotaEventCreate + conflict []sql.ConflictOption +} + +// Save creates the UserSubscriptionQuotaEvent entities in the database. +func (_c *UserSubscriptionQuotaEventCreateBulk) Save(ctx context.Context) ([]*UserSubscriptionQuotaEvent, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*UserSubscriptionQuotaEvent, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*UserSubscriptionQuotaEventMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = _c.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + if specs[i].ID.Value != nil { + id := specs[i].ID.Value.(int64) + nodes[i].ID = int64(id) + } + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *UserSubscriptionQuotaEventCreateBulk) SaveX(ctx context.Context) []*UserSubscriptionQuotaEvent { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *UserSubscriptionQuotaEventCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *UserSubscriptionQuotaEventCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.UserSubscriptionQuotaEvent.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.UserSubscriptionQuotaEventUpsert) { +// SetUserSubscriptionID(v+v). +// }). +// Exec(ctx) +func (_c *UserSubscriptionQuotaEventCreateBulk) OnConflict(opts ...sql.ConflictOption) *UserSubscriptionQuotaEventUpsertBulk { + _c.conflict = opts + return &UserSubscriptionQuotaEventUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *UserSubscriptionQuotaEventCreateBulk) OnConflictColumns(columns ...string) *UserSubscriptionQuotaEventUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &UserSubscriptionQuotaEventUpsertBulk{ + create: _c, + } +} + +// UserSubscriptionQuotaEventUpsertBulk is the builder for "upsert"-ing +// a bulk of UserSubscriptionQuotaEvent nodes. +type UserSubscriptionQuotaEventUpsertBulk struct { + create *UserSubscriptionQuotaEventCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// ). +// Exec(ctx) +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateNewValues() *UserSubscriptionQuotaEventUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.CreatedAt(); exists { + s.SetIgnore(usersubscriptionquotaevent.FieldCreatedAt) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.UserSubscriptionQuotaEvent.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *UserSubscriptionQuotaEventUpsertBulk) Ignore() *UserSubscriptionQuotaEventUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *UserSubscriptionQuotaEventUpsertBulk) DoNothing() *UserSubscriptionQuotaEventUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the UserSubscriptionQuotaEventCreateBulk.OnConflict +// documentation for more info. +func (u *UserSubscriptionQuotaEventUpsertBulk) Update(set func(*UserSubscriptionQuotaEventUpsert)) *UserSubscriptionQuotaEventUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&UserSubscriptionQuotaEventUpsert{UpdateSet: update}) + })) + return u +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetUserSubscriptionID(v) + }) +} + +// UpdateUserSubscriptionID sets the "user_subscription_id" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateUserSubscriptionID() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateUserSubscriptionID() + }) +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetQuotaTotalUsd(v) + }) +} + +// AddQuotaTotalUsd adds v to the "quota_total_usd" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) AddQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.AddQuotaTotalUsd(v) + }) +} + +// UpdateQuotaTotalUsd sets the "quota_total_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateQuotaTotalUsd() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateQuotaTotalUsd() + }) +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetQuotaUsedUsd(v) + }) +} + +// AddQuotaUsedUsd adds v to the "quota_used_usd" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) AddQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.AddQuotaUsedUsd(v) + }) +} + +// UpdateQuotaUsedUsd sets the "quota_used_usd" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateQuotaUsedUsd() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateQuotaUsedUsd() + }) +} + +// SetStartsAt sets the "starts_at" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetStartsAt(v) + }) +} + +// UpdateStartsAt sets the "starts_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateStartsAt() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateStartsAt() + }) +} + +// SetExpiresAt sets the "expires_at" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetExpiresAt(v) + }) +} + +// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateExpiresAt() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateExpiresAt() + }) +} + +// SetSourceKind sets the "source_kind" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetSourceKind(v string) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetSourceKind(v) + }) +} + +// UpdateSourceKind sets the "source_kind" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateSourceKind() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateSourceKind() + }) +} + +// SetSourceRef sets the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetSourceRef(v string) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetSourceRef(v) + }) +} + +// UpdateSourceRef sets the "source_ref" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateSourceRef() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateSourceRef() + }) +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) ClearSourceRef() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.ClearSourceRef() + }) +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *UserSubscriptionQuotaEventUpsertBulk) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *UserSubscriptionQuotaEventUpsertBulk) UpdateUpdatedAt() *UserSubscriptionQuotaEventUpsertBulk { + return u.Update(func(s *UserSubscriptionQuotaEventUpsert) { + s.UpdateUpdatedAt() + }) +} + +// Exec executes the query. +func (u *UserSubscriptionQuotaEventUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the UserSubscriptionQuotaEventCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("ent: missing options for UserSubscriptionQuotaEventCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *UserSubscriptionQuotaEventUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/usersubscriptionquotaevent_delete.go b/backend/ent/usersubscriptionquotaevent_delete.go new file mode 100644 index 00000000000..fb704cdbda3 --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" +) + +// UserSubscriptionQuotaEventDelete is the builder for deleting a UserSubscriptionQuotaEvent entity. +type UserSubscriptionQuotaEventDelete struct { + config + hooks []Hook + mutation *UserSubscriptionQuotaEventMutation +} + +// Where appends a list predicates to the UserSubscriptionQuotaEventDelete builder. +func (_d *UserSubscriptionQuotaEventDelete) Where(ps ...predicate.UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *UserSubscriptionQuotaEventDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserSubscriptionQuotaEventDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *UserSubscriptionQuotaEventDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(usersubscriptionquotaevent.Table, sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// UserSubscriptionQuotaEventDeleteOne is the builder for deleting a single UserSubscriptionQuotaEvent entity. +type UserSubscriptionQuotaEventDeleteOne struct { + _d *UserSubscriptionQuotaEventDelete +} + +// Where appends a list predicates to the UserSubscriptionQuotaEventDelete builder. +func (_d *UserSubscriptionQuotaEventDeleteOne) Where(ps ...predicate.UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *UserSubscriptionQuotaEventDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{usersubscriptionquotaevent.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *UserSubscriptionQuotaEventDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/backend/ent/usersubscriptionquotaevent_query.go b/backend/ent/usersubscriptionquotaevent_query.go new file mode 100644 index 00000000000..d389215dddb --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent_query.go @@ -0,0 +1,643 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" +) + +// UserSubscriptionQuotaEventQuery is the builder for querying UserSubscriptionQuotaEvent entities. +type UserSubscriptionQuotaEventQuery struct { + config + ctx *QueryContext + order []usersubscriptionquotaevent.OrderOption + inters []Interceptor + predicates []predicate.UserSubscriptionQuotaEvent + withSubscription *UserSubscriptionQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the UserSubscriptionQuotaEventQuery builder. +func (_q *UserSubscriptionQuotaEventQuery) Where(ps ...predicate.UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *UserSubscriptionQuotaEventQuery) Limit(limit int) *UserSubscriptionQuotaEventQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *UserSubscriptionQuotaEventQuery) Offset(offset int) *UserSubscriptionQuotaEventQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *UserSubscriptionQuotaEventQuery) Unique(unique bool) *UserSubscriptionQuotaEventQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *UserSubscriptionQuotaEventQuery) Order(o ...usersubscriptionquotaevent.OrderOption) *UserSubscriptionQuotaEventQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QuerySubscription chains the current query on the "subscription" edge. +func (_q *UserSubscriptionQuotaEventQuery) QuerySubscription() *UserSubscriptionQuery { + query := (&UserSubscriptionClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.FieldID, selector), + sqlgraph.To(usersubscription.Table, usersubscription.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, usersubscriptionquotaevent.SubscriptionTable, usersubscriptionquotaevent.SubscriptionColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first UserSubscriptionQuotaEvent entity from the query. +// Returns a *NotFoundError when no UserSubscriptionQuotaEvent was found. +func (_q *UserSubscriptionQuotaEventQuery) First(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{usersubscriptionquotaevent.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) FirstX(ctx context.Context) *UserSubscriptionQuotaEvent { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first UserSubscriptionQuotaEvent ID from the query. +// Returns a *NotFoundError when no UserSubscriptionQuotaEvent ID was found. +func (_q *UserSubscriptionQuotaEventQuery) FirstID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{usersubscriptionquotaevent.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) FirstIDX(ctx context.Context) int64 { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single UserSubscriptionQuotaEvent entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one UserSubscriptionQuotaEvent entity is found. +// Returns a *NotFoundError when no UserSubscriptionQuotaEvent entities are found. +func (_q *UserSubscriptionQuotaEventQuery) Only(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{usersubscriptionquotaevent.Label} + default: + return nil, &NotSingularError{usersubscriptionquotaevent.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) OnlyX(ctx context.Context) *UserSubscriptionQuotaEvent { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only UserSubscriptionQuotaEvent ID in the query. +// Returns a *NotSingularError when more than one UserSubscriptionQuotaEvent ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *UserSubscriptionQuotaEventQuery) OnlyID(ctx context.Context) (id int64, err error) { + var ids []int64 + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{usersubscriptionquotaevent.Label} + default: + err = &NotSingularError{usersubscriptionquotaevent.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) OnlyIDX(ctx context.Context) int64 { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of UserSubscriptionQuotaEvents. +func (_q *UserSubscriptionQuotaEventQuery) All(ctx context.Context) ([]*UserSubscriptionQuotaEvent, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*UserSubscriptionQuotaEvent, *UserSubscriptionQuotaEventQuery]() + return withInterceptors[[]*UserSubscriptionQuotaEvent](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) AllX(ctx context.Context) []*UserSubscriptionQuotaEvent { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of UserSubscriptionQuotaEvent IDs. +func (_q *UserSubscriptionQuotaEventQuery) IDs(ctx context.Context) (ids []int64, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(usersubscriptionquotaevent.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) IDsX(ctx context.Context) []int64 { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *UserSubscriptionQuotaEventQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*UserSubscriptionQuotaEventQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *UserSubscriptionQuotaEventQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("ent: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *UserSubscriptionQuotaEventQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the UserSubscriptionQuotaEventQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *UserSubscriptionQuotaEventQuery) Clone() *UserSubscriptionQuotaEventQuery { + if _q == nil { + return nil + } + return &UserSubscriptionQuotaEventQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]usersubscriptionquotaevent.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.UserSubscriptionQuotaEvent{}, _q.predicates...), + withSubscription: _q.withSubscription.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithSubscription tells the query-builder to eager-load the nodes that are connected to +// the "subscription" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *UserSubscriptionQuotaEventQuery) WithSubscription(opts ...func(*UserSubscriptionQuery)) *UserSubscriptionQuotaEventQuery { + query := (&UserSubscriptionClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSubscription = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// UserSubscriptionID int64 `json:"user_subscription_id,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.UserSubscriptionQuotaEvent.Query(). +// GroupBy(usersubscriptionquotaevent.FieldUserSubscriptionID). +// Aggregate(ent.Count()). +// Scan(ctx, &v) +func (_q *UserSubscriptionQuotaEventQuery) GroupBy(field string, fields ...string) *UserSubscriptionQuotaEventGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &UserSubscriptionQuotaEventGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = usersubscriptionquotaevent.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// UserSubscriptionID int64 `json:"user_subscription_id,omitempty"` +// } +// +// client.UserSubscriptionQuotaEvent.Query(). +// Select(usersubscriptionquotaevent.FieldUserSubscriptionID). +// Scan(ctx, &v) +func (_q *UserSubscriptionQuotaEventQuery) Select(fields ...string) *UserSubscriptionQuotaEventSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &UserSubscriptionQuotaEventSelect{UserSubscriptionQuotaEventQuery: _q} + sbuild.label = usersubscriptionquotaevent.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a UserSubscriptionQuotaEventSelect configured with the given aggregations. +func (_q *UserSubscriptionQuotaEventQuery) Aggregate(fns ...AggregateFunc) *UserSubscriptionQuotaEventSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *UserSubscriptionQuotaEventQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !usersubscriptionquotaevent.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *UserSubscriptionQuotaEventQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserSubscriptionQuotaEvent, error) { + var ( + nodes = []*UserSubscriptionQuotaEvent{} + _spec = _q.querySpec() + loadedTypes = [1]bool{ + _q.withSubscription != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*UserSubscriptionQuotaEvent).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &UserSubscriptionQuotaEvent{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withSubscription; query != nil { + if err := _q.loadSubscription(ctx, query, nodes, nil, + func(n *UserSubscriptionQuotaEvent, e *UserSubscription) { n.Edges.Subscription = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *UserSubscriptionQuotaEventQuery) loadSubscription(ctx context.Context, query *UserSubscriptionQuery, nodes []*UserSubscriptionQuotaEvent, init func(*UserSubscriptionQuotaEvent), assign func(*UserSubscriptionQuotaEvent, *UserSubscription)) error { + ids := make([]int64, 0, len(nodes)) + nodeids := make(map[int64][]*UserSubscriptionQuotaEvent) + for i := range nodes { + fk := nodes[i].UserSubscriptionID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(usersubscription.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "user_subscription_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *UserSubscriptionQuotaEventQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *UserSubscriptionQuotaEventQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.Columns, sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usersubscriptionquotaevent.FieldID) + for i := range fields { + if fields[i] != usersubscriptionquotaevent.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withSubscription != nil { + _spec.Node.AddColumnOnce(usersubscriptionquotaevent.FieldUserSubscriptionID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *UserSubscriptionQuotaEventQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(usersubscriptionquotaevent.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = usersubscriptionquotaevent.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, m := range _q.modifiers { + m(selector) + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (_q *UserSubscriptionQuotaEventQuery) ForUpdate(opts ...sql.LockOption) *UserSubscriptionQuotaEventQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return _q +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (_q *UserSubscriptionQuotaEventQuery) ForShare(opts ...sql.LockOption) *UserSubscriptionQuotaEventQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// UserSubscriptionQuotaEventGroupBy is the group-by builder for UserSubscriptionQuotaEvent entities. +type UserSubscriptionQuotaEventGroupBy struct { + selector + build *UserSubscriptionQuotaEventQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *UserSubscriptionQuotaEventGroupBy) Aggregate(fns ...AggregateFunc) *UserSubscriptionQuotaEventGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *UserSubscriptionQuotaEventGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserSubscriptionQuotaEventQuery, *UserSubscriptionQuotaEventGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *UserSubscriptionQuotaEventGroupBy) sqlScan(ctx context.Context, root *UserSubscriptionQuotaEventQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// UserSubscriptionQuotaEventSelect is the builder for selecting fields of UserSubscriptionQuotaEvent entities. +type UserSubscriptionQuotaEventSelect struct { + *UserSubscriptionQuotaEventQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *UserSubscriptionQuotaEventSelect) Aggregate(fns ...AggregateFunc) *UserSubscriptionQuotaEventSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *UserSubscriptionQuotaEventSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*UserSubscriptionQuotaEventQuery, *UserSubscriptionQuotaEventSelect](ctx, _s.UserSubscriptionQuotaEventQuery, _s, _s.inters, v) +} + +func (_s *UserSubscriptionQuotaEventSelect) sqlScan(ctx context.Context, root *UserSubscriptionQuotaEventQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/backend/ent/usersubscriptionquotaevent_update.go b/backend/ent/usersubscriptionquotaevent_update.go new file mode 100644 index 00000000000..4140936fe0b --- /dev/null +++ b/backend/ent/usersubscriptionquotaevent_update.go @@ -0,0 +1,627 @@ +// Code generated by ent, DO NOT EDIT. + +package ent + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/Wei-Shaw/sub2api/ent/predicate" + "github.com/Wei-Shaw/sub2api/ent/usersubscription" + "github.com/Wei-Shaw/sub2api/ent/usersubscriptionquotaevent" +) + +// UserSubscriptionQuotaEventUpdate is the builder for updating UserSubscriptionQuotaEvent entities. +type UserSubscriptionQuotaEventUpdate struct { + config + hooks []Hook + mutation *UserSubscriptionQuotaEventMutation +} + +// Where appends a list predicates to the UserSubscriptionQuotaEventUpdate builder. +func (_u *UserSubscriptionQuotaEventUpdate) Where(ps ...predicate.UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetUserSubscriptionID(v) + return _u +} + +// SetNillableUserSubscriptionID sets the "user_subscription_id" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableUserSubscriptionID(v *int64) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetUserSubscriptionID(*v) + } + return _u +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.ResetQuotaTotalUsd() + _u.mutation.SetQuotaTotalUsd(v) + return _u +} + +// SetNillableQuotaTotalUsd sets the "quota_total_usd" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableQuotaTotalUsd(v *float64) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetQuotaTotalUsd(*v) + } + return _u +} + +// AddQuotaTotalUsd adds value to the "quota_total_usd" field. +func (_u *UserSubscriptionQuotaEventUpdate) AddQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.AddQuotaTotalUsd(v) + return _u +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.ResetQuotaUsedUsd() + _u.mutation.SetQuotaUsedUsd(v) + return _u +} + +// SetNillableQuotaUsedUsd sets the "quota_used_usd" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableQuotaUsedUsd(v *float64) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetQuotaUsedUsd(*v) + } + return _u +} + +// AddQuotaUsedUsd adds value to the "quota_used_usd" field. +func (_u *UserSubscriptionQuotaEventUpdate) AddQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.AddQuotaUsedUsd(v) + return _u +} + +// SetStartsAt sets the "starts_at" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetStartsAt(v) + return _u +} + +// SetNillableStartsAt sets the "starts_at" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableStartsAt(v *time.Time) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetStartsAt(*v) + } + return _u +} + +// SetExpiresAt sets the "expires_at" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetExpiresAt(v) + return _u +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableExpiresAt(v *time.Time) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetExpiresAt(*v) + } + return _u +} + +// SetSourceKind sets the "source_kind" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetSourceKind(v string) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetSourceKind(v) + return _u +} + +// SetNillableSourceKind sets the "source_kind" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableSourceKind(v *string) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetSourceKind(*v) + } + return _u +} + +// SetSourceRef sets the "source_ref" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetSourceRef(v string) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetSourceRef(v) + return _u +} + +// SetNillableSourceRef sets the "source_ref" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdate) SetNillableSourceRef(v *string) *UserSubscriptionQuotaEventUpdate { + if v != nil { + _u.SetSourceRef(*v) + } + return _u +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (_u *UserSubscriptionQuotaEventUpdate) ClearSourceRef() *UserSubscriptionQuotaEventUpdate { + _u.mutation.ClearSourceRef() + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *UserSubscriptionQuotaEventUpdate) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetSubscriptionID sets the "subscription" edge to the UserSubscription entity by ID. +func (_u *UserSubscriptionQuotaEventUpdate) SetSubscriptionID(id int64) *UserSubscriptionQuotaEventUpdate { + _u.mutation.SetSubscriptionID(id) + return _u +} + +// SetSubscription sets the "subscription" edge to the UserSubscription entity. +func (_u *UserSubscriptionQuotaEventUpdate) SetSubscription(v *UserSubscription) *UserSubscriptionQuotaEventUpdate { + return _u.SetSubscriptionID(v.ID) +} + +// Mutation returns the UserSubscriptionQuotaEventMutation object of the builder. +func (_u *UserSubscriptionQuotaEventUpdate) Mutation() *UserSubscriptionQuotaEventMutation { + return _u.mutation +} + +// ClearSubscription clears the "subscription" edge to the UserSubscription entity. +func (_u *UserSubscriptionQuotaEventUpdate) ClearSubscription() *UserSubscriptionQuotaEventUpdate { + _u.mutation.ClearSubscription() + return _u +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *UserSubscriptionQuotaEventUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserSubscriptionQuotaEventUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *UserSubscriptionQuotaEventUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserSubscriptionQuotaEventUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *UserSubscriptionQuotaEventUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := usersubscriptionquotaevent.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *UserSubscriptionQuotaEventUpdate) check() error { + if v, ok := _u.mutation.SourceKind(); ok { + if err := usersubscriptionquotaevent.SourceKindValidator(v); err != nil { + return &ValidationError{Name: "source_kind", err: fmt.Errorf(`ent: validator failed for field "UserSubscriptionQuotaEvent.source_kind": %w`, err)} + } + } + if _u.mutation.SubscriptionCleared() && len(_u.mutation.SubscriptionIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "UserSubscriptionQuotaEvent.subscription"`) + } + return nil +} + +func (_u *UserSubscriptionQuotaEventUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.Columns, sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.QuotaTotalUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaTotalUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedQuotaTotalUsd(); ok { + _spec.AddField(usersubscriptionquotaevent.FieldQuotaTotalUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.QuotaUsedUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaUsedUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedQuotaUsedUsd(); ok { + _spec.AddField(usersubscriptionquotaevent.FieldQuotaUsedUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.StartsAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldStartsAt, field.TypeTime, value) + } + if value, ok := _u.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := _u.mutation.SourceKind(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceKind, field.TypeString, value) + } + if value, ok := _u.mutation.SourceRef(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceRef, field.TypeString, value) + } + if _u.mutation.SourceRefCleared() { + _spec.ClearField(usersubscriptionquotaevent.FieldSourceRef, field.TypeString) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.SubscriptionCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscriptionquotaevent.SubscriptionTable, + Columns: []string{usersubscriptionquotaevent.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscriptionquotaevent.SubscriptionTable, + Columns: []string{usersubscriptionquotaevent.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usersubscriptionquotaevent.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// UserSubscriptionQuotaEventUpdateOne is the builder for updating a single UserSubscriptionQuotaEvent entity. +type UserSubscriptionQuotaEventUpdateOne struct { + config + fields []string + hooks []Hook + mutation *UserSubscriptionQuotaEventMutation +} + +// SetUserSubscriptionID sets the "user_subscription_id" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetUserSubscriptionID(v int64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetUserSubscriptionID(v) + return _u +} + +// SetNillableUserSubscriptionID sets the "user_subscription_id" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableUserSubscriptionID(v *int64) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetUserSubscriptionID(*v) + } + return _u +} + +// SetQuotaTotalUsd sets the "quota_total_usd" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.ResetQuotaTotalUsd() + _u.mutation.SetQuotaTotalUsd(v) + return _u +} + +// SetNillableQuotaTotalUsd sets the "quota_total_usd" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableQuotaTotalUsd(v *float64) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetQuotaTotalUsd(*v) + } + return _u +} + +// AddQuotaTotalUsd adds value to the "quota_total_usd" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) AddQuotaTotalUsd(v float64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.AddQuotaTotalUsd(v) + return _u +} + +// SetQuotaUsedUsd sets the "quota_used_usd" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.ResetQuotaUsedUsd() + _u.mutation.SetQuotaUsedUsd(v) + return _u +} + +// SetNillableQuotaUsedUsd sets the "quota_used_usd" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableQuotaUsedUsd(v *float64) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetQuotaUsedUsd(*v) + } + return _u +} + +// AddQuotaUsedUsd adds value to the "quota_used_usd" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) AddQuotaUsedUsd(v float64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.AddQuotaUsedUsd(v) + return _u +} + +// SetStartsAt sets the "starts_at" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetStartsAt(v time.Time) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetStartsAt(v) + return _u +} + +// SetNillableStartsAt sets the "starts_at" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableStartsAt(v *time.Time) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetStartsAt(*v) + } + return _u +} + +// SetExpiresAt sets the "expires_at" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetExpiresAt(v time.Time) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetExpiresAt(v) + return _u +} + +// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableExpiresAt(v *time.Time) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetExpiresAt(*v) + } + return _u +} + +// SetSourceKind sets the "source_kind" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetSourceKind(v string) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetSourceKind(v) + return _u +} + +// SetNillableSourceKind sets the "source_kind" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableSourceKind(v *string) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetSourceKind(*v) + } + return _u +} + +// SetSourceRef sets the "source_ref" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetSourceRef(v string) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetSourceRef(v) + return _u +} + +// SetNillableSourceRef sets the "source_ref" field if the given value is not nil. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetNillableSourceRef(v *string) *UserSubscriptionQuotaEventUpdateOne { + if v != nil { + _u.SetSourceRef(*v) + } + return _u +} + +// ClearSourceRef clears the value of the "source_ref" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) ClearSourceRef() *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.ClearSourceRef() + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetUpdatedAt(v time.Time) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetSubscriptionID sets the "subscription" edge to the UserSubscription entity by ID. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetSubscriptionID(id int64) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.SetSubscriptionID(id) + return _u +} + +// SetSubscription sets the "subscription" edge to the UserSubscription entity. +func (_u *UserSubscriptionQuotaEventUpdateOne) SetSubscription(v *UserSubscription) *UserSubscriptionQuotaEventUpdateOne { + return _u.SetSubscriptionID(v.ID) +} + +// Mutation returns the UserSubscriptionQuotaEventMutation object of the builder. +func (_u *UserSubscriptionQuotaEventUpdateOne) Mutation() *UserSubscriptionQuotaEventMutation { + return _u.mutation +} + +// ClearSubscription clears the "subscription" edge to the UserSubscription entity. +func (_u *UserSubscriptionQuotaEventUpdateOne) ClearSubscription() *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.ClearSubscription() + return _u +} + +// Where appends a list predicates to the UserSubscriptionQuotaEventUpdate builder. +func (_u *UserSubscriptionQuotaEventUpdateOne) Where(ps ...predicate.UserSubscriptionQuotaEvent) *UserSubscriptionQuotaEventUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *UserSubscriptionQuotaEventUpdateOne) Select(field string, fields ...string) *UserSubscriptionQuotaEventUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated UserSubscriptionQuotaEvent entity. +func (_u *UserSubscriptionQuotaEventUpdateOne) Save(ctx context.Context) (*UserSubscriptionQuotaEvent, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *UserSubscriptionQuotaEventUpdateOne) SaveX(ctx context.Context) *UserSubscriptionQuotaEvent { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *UserSubscriptionQuotaEventUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *UserSubscriptionQuotaEventUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *UserSubscriptionQuotaEventUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := usersubscriptionquotaevent.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *UserSubscriptionQuotaEventUpdateOne) check() error { + if v, ok := _u.mutation.SourceKind(); ok { + if err := usersubscriptionquotaevent.SourceKindValidator(v); err != nil { + return &ValidationError{Name: "source_kind", err: fmt.Errorf(`ent: validator failed for field "UserSubscriptionQuotaEvent.source_kind": %w`, err)} + } + } + if _u.mutation.SubscriptionCleared() && len(_u.mutation.SubscriptionIDs()) > 0 { + return errors.New(`ent: clearing a required unique edge "UserSubscriptionQuotaEvent.subscription"`) + } + return nil +} + +func (_u *UserSubscriptionQuotaEventUpdateOne) sqlSave(ctx context.Context) (_node *UserSubscriptionQuotaEvent, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(usersubscriptionquotaevent.Table, usersubscriptionquotaevent.Columns, sqlgraph.NewFieldSpec(usersubscriptionquotaevent.FieldID, field.TypeInt64)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "UserSubscriptionQuotaEvent.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, usersubscriptionquotaevent.FieldID) + for _, f := range fields { + if !usersubscriptionquotaevent.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)} + } + if f != usersubscriptionquotaevent.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.QuotaTotalUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaTotalUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedQuotaTotalUsd(); ok { + _spec.AddField(usersubscriptionquotaevent.FieldQuotaTotalUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.QuotaUsedUsd(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldQuotaUsedUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedQuotaUsedUsd(); ok { + _spec.AddField(usersubscriptionquotaevent.FieldQuotaUsedUsd, field.TypeFloat64, value) + } + if value, ok := _u.mutation.StartsAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldStartsAt, field.TypeTime, value) + } + if value, ok := _u.mutation.ExpiresAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldExpiresAt, field.TypeTime, value) + } + if value, ok := _u.mutation.SourceKind(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceKind, field.TypeString, value) + } + if value, ok := _u.mutation.SourceRef(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldSourceRef, field.TypeString, value) + } + if _u.mutation.SourceRefCleared() { + _spec.ClearField(usersubscriptionquotaevent.FieldSourceRef, field.TypeString) + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(usersubscriptionquotaevent.FieldUpdatedAt, field.TypeTime, value) + } + if _u.mutation.SubscriptionCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscriptionquotaevent.SubscriptionTable, + Columns: []string{usersubscriptionquotaevent.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeInt64), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: usersubscriptionquotaevent.SubscriptionTable, + Columns: []string{usersubscriptionquotaevent.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(usersubscription.FieldID, field.TypeInt64), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } + _node = &UserSubscriptionQuotaEvent{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{usersubscriptionquotaevent.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/backend/go.sum b/backend/go.sum index bb33293b7b5..c4e6c9f00d5 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -187,6 +187,8 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI= github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 044e07d7a23..b1a3ffecee0 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -59,6 +59,7 @@ const ( const ( SubscriptionTypeStandard = "standard" // 标准计费模式(按余额扣费) SubscriptionTypeSubscription = "subscription" // 订阅模式(按限额控制) + SubscriptionTypeTotalQuota = "total_quota" // 总额度订阅模式(按事件总额度控制) ) // Subscription status constants diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go index 7545c14f5af..90b88adf1a2 100644 --- a/backend/internal/handler/admin/group_handler.go +++ b/backend/internal/handler/admin/group_handler.go @@ -74,6 +74,13 @@ func (f optionalLimitField) ToServiceInput() *float64 { return &zero } +func (f optionalLimitField) ToServiceInputPreserveNull() *float64 { + if !f.set { + return nil + } + return f.value +} + // NewGroupHandler creates a new admin group handler func NewGroupHandler(adminService service.AdminService, dashboardService *service.DashboardService, groupCapacityService *service.GroupCapacityService) *GroupHandler { return &GroupHandler{ @@ -90,10 +97,11 @@ type CreateGroupRequest struct { Platform string `json:"platform" binding:"omitempty,oneof=anthropic openai gemini antigravity grok"` RateMultiplier float64 `json:"rate_multiplier"` IsExclusive bool `json:"is_exclusive"` - SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"` + SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription total_quota"` DailyLimitUSD optionalLimitField `json:"daily_limit_usd"` WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"` MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"` + TotalLimitUSD optionalLimitField `json:"total_limit_usd"` // 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置) AllowImageGeneration bool `json:"allow_image_generation"` AllowBatchImageGeneration bool `json:"allow_batch_image_generation"` @@ -148,10 +156,11 @@ type UpdateGroupRequest struct { RateMultiplier *float64 `json:"rate_multiplier"` IsExclusive *bool `json:"is_exclusive"` Status string `json:"status" binding:"omitempty,oneof=active inactive"` - SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"` + SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription total_quota"` DailyLimitUSD optionalLimitField `json:"daily_limit_usd"` WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"` MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"` + TotalLimitUSD optionalLimitField `json:"total_limit_usd"` // 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置) AllowImageGeneration *bool `json:"allow_image_generation"` AllowBatchImageGeneration *bool `json:"allow_batch_image_generation"` @@ -314,6 +323,15 @@ func (h *GroupHandler) Create(c *gin.Context) { return } + dayLimit := req.DailyLimitUSD.ToServiceInput() + weekLimit := req.WeeklyLimitUSD.ToServiceInput() + monthLimit := req.MonthlyLimitUSD.ToServiceInput() + if req.SubscriptionType == service.SubscriptionTypeTotalQuota { + dayLimit = req.DailyLimitUSD.ToServiceInputPreserveNull() + weekLimit = req.WeeklyLimitUSD.ToServiceInputPreserveNull() + monthLimit = req.MonthlyLimitUSD.ToServiceInputPreserveNull() + } + if err := service.ValidatePeakRateConfig(req.SubscriptionType, req.PeakRateEnabled, req.PeakStart, req.PeakEnd, float64ValueOrDefault(req.PeakRateMultiplier, 1.0)); err != nil { response.BadRequest(c, err.Error()) return @@ -326,9 +344,10 @@ func (h *GroupHandler) Create(c *gin.Context) { RateMultiplier: req.RateMultiplier, IsExclusive: req.IsExclusive, SubscriptionType: req.SubscriptionType, - DailyLimitUSD: req.DailyLimitUSD.ToServiceInput(), - WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(), - MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(), + DailyLimitUSD: dayLimit, + WeeklyLimitUSD: weekLimit, + MonthlyLimitUSD: monthLimit, + TotalLimitUSD: req.TotalLimitUSD.ToServiceInput(), AllowImageGeneration: req.AllowImageGeneration, AllowBatchImageGeneration: req.AllowBatchImageGeneration, ImageRateIndependent: req.ImageRateIndependent, @@ -436,6 +455,15 @@ func (h *GroupHandler) Update(c *gin.Context) { return } + dayLimit := req.DailyLimitUSD.ToServiceInput() + weekLimit := req.WeeklyLimitUSD.ToServiceInput() + monthLimit := req.MonthlyLimitUSD.ToServiceInput() + if req.SubscriptionType == service.SubscriptionTypeTotalQuota { + dayLimit = req.DailyLimitUSD.ToServiceInputPreserveNull() + weekLimit = req.WeeklyLimitUSD.ToServiceInputPreserveNull() + monthLimit = req.MonthlyLimitUSD.ToServiceInputPreserveNull() + } + group, err := h.adminService.UpdateGroup(c.Request.Context(), groupID, &service.UpdateGroupInput{ Name: req.Name, Description: req.Description, @@ -444,9 +472,10 @@ func (h *GroupHandler) Update(c *gin.Context) { IsExclusive: req.IsExclusive, Status: req.Status, SubscriptionType: req.SubscriptionType, - DailyLimitUSD: req.DailyLimitUSD.ToServiceInput(), - WeeklyLimitUSD: req.WeeklyLimitUSD.ToServiceInput(), - MonthlyLimitUSD: req.MonthlyLimitUSD.ToServiceInput(), + DailyLimitUSD: dayLimit, + WeeklyLimitUSD: weekLimit, + MonthlyLimitUSD: monthLimit, + TotalLimitUSD: req.TotalLimitUSD.ToServiceInput(), AllowImageGeneration: req.AllowImageGeneration, AllowBatchImageGeneration: req.AllowBatchImageGeneration, ImageRateIndependent: req.ImageRateIndependent, diff --git a/backend/internal/handler/admin/subscription_handler.go b/backend/internal/handler/admin/subscription_handler.go index d370dad8863..19d28361202 100644 --- a/backend/internal/handler/admin/subscription_handler.go +++ b/backend/internal/handler/admin/subscription_handler.go @@ -145,11 +145,13 @@ func (h *SubscriptionHandler) Assign(c *gin.Context) { adminID := getAdminIDFromContext(c) subscription, err := h.subscriptionService.AssignSubscription(c.Request.Context(), &service.AssignSubscriptionInput{ - UserID: req.UserID, - GroupID: req.GroupID, - ValidityDays: req.ValidityDays, - AssignedBy: adminID, - Notes: req.Notes, + UserID: req.UserID, + GroupID: req.GroupID, + ValidityDays: req.ValidityDays, + AssignedBy: adminID, + Notes: req.Notes, + EventSourceKind: service.SubscriptionQuotaSourceAdminAssign, + EventSourceRef: "admin:" + strconv.FormatInt(adminID, 10), }) if err != nil { response.ErrorFrom(c, err) diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 6f82e29c06e..054c2820430 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -181,6 +181,7 @@ func groupFromServiceBase(g *service.Group) Group { DailyLimitUSD: g.DailyLimitUSD, WeeklyLimitUSD: g.WeeklyLimitUSD, MonthlyLimitUSD: g.MonthlyLimitUSD, + TotalLimitUSD: g.TotalLimitUSD, AllowImageGeneration: g.AllowImageGeneration, AllowBatchImageGeneration: g.AllowBatchImageGeneration, ImageRateIndependent: g.ImageRateIndependent, @@ -774,6 +775,11 @@ func userSubscriptionFromServiceBase(sub *service.UserSubscription) UserSubscrip DailyUsageUSD: sub.DailyUsageUSD, WeeklyUsageUSD: sub.WeeklyUsageUSD, MonthlyUsageUSD: sub.MonthlyUsageUSD, + TotalLimitUSD: sub.TotalLimitUSD, + TotalUsedUSD: sub.TotalUsedUSD, + TotalRemainingUSD: sub.TotalRemainingUSD, + NextExpiringQuotaUSD: sub.NextExpiringQuotaUSD, + NextQuotaExpireAt: sub.NextQuotaExpireAt, CreatedAt: sub.CreatedAt, UpdatedAt: sub.UpdatedAt, RevokedAt: sub.DeletedAt, diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go index 117091f4102..43f757b8222 100644 --- a/backend/internal/handler/dto/types.go +++ b/backend/internal/handler/dto/types.go @@ -99,6 +99,7 @@ type Group struct { DailyLimitUSD *float64 `json:"daily_limit_usd"` WeeklyLimitUSD *float64 `json:"weekly_limit_usd"` MonthlyLimitUSD *float64 `json:"monthly_limit_usd"` + TotalLimitUSD *float64 `json:"total_limit_usd"` // 图片生成计费配置(仅 antigravity 平台使用) AllowImageGeneration bool `json:"allow_image_generation"` @@ -619,6 +620,11 @@ type UserSubscription struct { DailyUsageUSD float64 `json:"daily_usage_usd"` WeeklyUsageUSD float64 `json:"weekly_usage_usd"` MonthlyUsageUSD float64 `json:"monthly_usage_usd"` + TotalLimitUSD float64 `json:"total_limit_usd"` + TotalUsedUSD float64 `json:"total_used_usd"` + TotalRemainingUSD float64 `json:"total_remaining_usd"` + NextExpiringQuotaUSD float64 `json:"next_expiring_quota_usd"` + NextQuotaExpireAt *time.Time `json:"next_quota_expire_at"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go index 891878799aa..1bdf44cf425 100644 --- a/backend/internal/handler/gateway_handler.go +++ b/backend/internal/handler/gateway_handler.go @@ -1534,14 +1534,20 @@ func (h *GatewayHandler) usageUnrestricted(c *gin.Context, ctx context.Context, remaining := h.calculateSubscriptionRemaining(apiKey.Group, subscription) resp["remaining"] = remaining resp["subscription"] = gin.H{ - "daily_usage_usd": subscription.DailyUsageUSD, - "weekly_usage_usd": subscription.WeeklyUsageUSD, - "monthly_usage_usd": subscription.MonthlyUsageUSD, - "daily_limit_usd": apiKey.Group.DailyLimitUSD, - "weekly_limit_usd": apiKey.Group.WeeklyLimitUSD, - "monthly_limit_usd": apiKey.Group.MonthlyLimitUSD, - "weekly_window_start": subscription.WeeklyWindowStart, - "expires_at": subscription.ExpiresAt, + "daily_usage_usd": subscription.DailyUsageUSD, + "weekly_usage_usd": subscription.WeeklyUsageUSD, + "monthly_usage_usd": subscription.MonthlyUsageUSD, + "total_limit_usd": subscription.TotalLimitUSD, + "total_used_usd": subscription.TotalUsedUSD, + "total_remaining_usd": subscription.TotalRemainingUSD, + "next_quota_expire_at": subscription.NextQuotaExpireAt, + "next_expiring_quota_usd": subscription.NextExpiringQuotaUSD, + "daily_limit_usd": apiKey.Group.DailyLimitUSD, + "weekly_limit_usd": apiKey.Group.WeeklyLimitUSD, + "monthly_limit_usd": apiKey.Group.MonthlyLimitUSD, + "total_limit_group_usd": apiKey.Group.TotalLimitUSD, + "weekly_window_start": subscription.WeeklyWindowStart, + "expires_at": subscription.ExpiresAt, } } @@ -1590,6 +1596,9 @@ func (h *GatewayHandler) usageUnrestricted(c *gin.Context, ctx context.Context, // 1. 如果日/周/月任一限额达到100%,返回0 // 2. 否则返回所有已配置周期中剩余额度的最小值 func (h *GatewayHandler) calculateSubscriptionRemaining(group *service.Group, sub *service.UserSubscription) float64 { + if group.IsTotalQuotaSubscriptionType() { + return sub.TotalRemainingUSD + } var remainingValues []float64 // 检查日限额 diff --git a/backend/internal/handler/subscription_handler.go b/backend/internal/handler/subscription_handler.go index b40df833365..7f7c5e5ebec 100644 --- a/backend/internal/handler/subscription_handler.go +++ b/backend/internal/handler/subscription_handler.go @@ -21,6 +21,11 @@ type SubscriptionSummaryItem struct { WeeklyLimitUSD float64 `json:"weekly_limit_usd,omitempty"` MonthlyUsedUSD float64 `json:"monthly_used_usd,omitempty"` MonthlyLimitUSD float64 `json:"monthly_limit_usd,omitempty"` + TotalLimitUSD float64 `json:"total_limit_usd,omitempty"` + TotalUsedUSD float64 `json:"total_used_usd,omitempty"` + TotalRemainingUSD float64 `json:"total_remaining_usd,omitempty"` + NextExpiringQuotaUSD float64 `json:"next_expiring_quota_usd,omitempty"` + NextQuotaExpireAt *string `json:"next_quota_expire_at,omitempty"` ExpiresAt *string `json:"expires_at,omitempty"` } @@ -160,6 +165,16 @@ func (h *SubscriptionHandler) GetSummary(c *gin.Context) { if sub.Group.MonthlyLimitUSD != nil { item.MonthlyLimitUSD = *sub.Group.MonthlyLimitUSD } + if sub.Group.TotalLimitUSD != nil { + item.TotalLimitUSD = *sub.Group.TotalLimitUSD + } + } + item.TotalUsedUSD = sub.TotalUsedUSD + item.TotalRemainingUSD = sub.TotalRemainingUSD + item.NextExpiringQuotaUSD = sub.NextExpiringQuotaUSD + if sub.NextQuotaExpireAt != nil { + formatted := sub.NextQuotaExpireAt.Format("2006-01-02T15:04:05Z07:00") + item.NextQuotaExpireAt = &formatted } // Format expiration time @@ -168,8 +183,11 @@ func (h *SubscriptionHandler) GetSummary(c *gin.Context) { item.ExpiresAt = &formatted } - // Track total usage (use monthly as the most comprehensive) - totalUsed += sub.MonthlyUsageUSD + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + totalUsed += sub.TotalUsedUSD + } else { + totalUsed += sub.MonthlyUsageUSD + } items = append(items, item) } diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go index 8addad4342a..3d81222ea98 100644 --- a/backend/internal/repository/api_key_repo.go +++ b/backend/internal/repository/api_key_repo.go @@ -178,6 +178,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se group.FieldDailyLimitUsd, group.FieldWeeklyLimitUsd, group.FieldMonthlyLimitUsd, + group.FieldTotalLimitUsd, group.FieldAllowImageGeneration, group.FieldAllowBatchImageGeneration, group.FieldImageRateIndependent, @@ -923,6 +924,7 @@ func groupEntityToService(g *dbent.Group) *service.Group { DailyLimitUSD: g.DailyLimitUsd, WeeklyLimitUSD: g.WeeklyLimitUsd, MonthlyLimitUSD: g.MonthlyLimitUsd, + TotalLimitUSD: g.TotalLimitUsd, AllowImageGeneration: g.AllowImageGeneration, AllowBatchImageGeneration: g.AllowBatchImageGeneration, ImageRateIndependent: g.ImageRateIndependent, diff --git a/backend/internal/repository/billing_cache.go b/backend/internal/repository/billing_cache.go index af8ec49c08e..e9458f0da26 100644 --- a/backend/internal/repository/billing_cache.go +++ b/backend/internal/repository/billing_cache.go @@ -55,6 +55,11 @@ const ( subFieldDailyUsage = "daily_usage" subFieldWeeklyUsage = "weekly_usage" subFieldMonthlyUsage = "monthly_usage" + subFieldTotalLimit = "total_limit" + subFieldTotalUsed = "total_used" + subFieldTotalRemain = "total_remaining" + subFieldNextExpireAt = "next_quota_expire_at" + subFieldNextExpiring = "next_expiring_quota_usd" subFieldVersion = "version" ) @@ -211,6 +216,24 @@ func (c *billingCache) parseSubscriptionCache(data map[string]string) (*service. if monthlyStr, ok := data[subFieldMonthlyUsage]; ok { result.MonthlyUsage, _ = strconv.ParseFloat(monthlyStr, 64) } + if totalLimitStr, ok := data[subFieldTotalLimit]; ok { + result.TotalLimit, _ = strconv.ParseFloat(totalLimitStr, 64) + } + if totalUsedStr, ok := data[subFieldTotalUsed]; ok { + result.TotalUsed, _ = strconv.ParseFloat(totalUsedStr, 64) + } + if totalRemainStr, ok := data[subFieldTotalRemain]; ok { + result.TotalRemaining, _ = strconv.ParseFloat(totalRemainStr, 64) + } + if nextExpireStr, ok := data[subFieldNextExpireAt]; ok && nextExpireStr != "" { + if unix, err := strconv.ParseInt(nextExpireStr, 10, 64); err == nil && unix > 0 { + t := time.Unix(unix, 0) + result.NextQuotaExpireAt = &t + } + } + if nextExpiringStr, ok := data[subFieldNextExpiring]; ok { + result.NextExpiringQuotaUSD, _ = strconv.ParseFloat(nextExpiringStr, 64) + } if versionStr, ok := data[subFieldVersion]; ok { result.Version, _ = strconv.ParseInt(versionStr, 10, 64) @@ -232,8 +255,17 @@ func (c *billingCache) SetSubscriptionCache(ctx context.Context, userID, groupID subFieldDailyUsage: data.DailyUsage, subFieldWeeklyUsage: data.WeeklyUsage, subFieldMonthlyUsage: data.MonthlyUsage, + subFieldTotalLimit: data.TotalLimit, + subFieldTotalUsed: data.TotalUsed, + subFieldTotalRemain: data.TotalRemaining, + subFieldNextExpiring: data.NextExpiringQuotaUSD, subFieldVersion: data.Version, } + if data.NextQuotaExpireAt != nil { + fields[subFieldNextExpireAt] = data.NextQuotaExpireAt.Unix() + } else { + fields[subFieldNextExpireAt] = 0 + } pipe := c.rdb.Pipeline() pipe.HSet(ctx, key, fields) diff --git a/backend/internal/repository/fixtures_integration_test.go b/backend/internal/repository/fixtures_integration_test.go index 48c33364c39..8997ad881db 100644 --- a/backend/internal/repository/fixtures_integration_test.go +++ b/backend/internal/repository/fixtures_integration_test.go @@ -102,6 +102,9 @@ func mustCreateGroup(t *testing.T, client *dbent.Client, g *service.Group) *serv if g.MonthlyLimitUSD != nil { create.SetMonthlyLimitUsd(*g.MonthlyLimitUSD) } + if g.TotalLimitUSD != nil { + create.SetTotalLimitUsd(*g.TotalLimitUSD) + } if !g.CreatedAt.IsZero() { create.SetCreatedAt(g.CreatedAt) } diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go index 2b770ffd7f1..1e7adc526d6 100644 --- a/backend/internal/repository/group_repo.go +++ b/backend/internal/repository/group_repo.go @@ -68,6 +68,7 @@ func createGroupRecord(ctx context.Context, client *dbent.Client, groupIn *servi SetNillableDailyLimitUsd(groupIn.DailyLimitUSD). SetNillableWeeklyLimitUsd(groupIn.WeeklyLimitUSD). SetNillableMonthlyLimitUsd(groupIn.MonthlyLimitUSD). + SetNillableTotalLimitUsd(groupIn.TotalLimitUSD). SetAllowImageGeneration(groupIn.AllowImageGeneration). SetAllowBatchImageGeneration(groupIn.AllowBatchImageGeneration). SetImageRateIndependent(groupIn.ImageRateIndependent). @@ -236,6 +237,7 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er SetNillableDailyLimitUsd(groupIn.DailyLimitUSD). SetNillableWeeklyLimitUsd(groupIn.WeeklyLimitUSD). SetNillableMonthlyLimitUsd(groupIn.MonthlyLimitUSD). + SetNillableTotalLimitUsd(groupIn.TotalLimitUSD). SetAllowImageGeneration(groupIn.AllowImageGeneration). SetAllowBatchImageGeneration(groupIn.AllowBatchImageGeneration). SetImageRateIndependent(groupIn.ImageRateIndependent). @@ -284,6 +286,11 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er } else { builder = builder.ClearMonthlyLimitUsd() } + if groupIn.TotalLimitUSD != nil { + builder = builder.SetTotalLimitUsd(*groupIn.TotalLimitUSD) + } else { + builder = builder.ClearTotalLimitUsd() + } if groupIn.ImagePrice1K != nil { builder = builder.SetImagePrice1k(*groupIn.ImagePrice1K) } else { diff --git a/backend/internal/repository/usage_billing_repo.go b/backend/internal/repository/usage_billing_repo.go index ce0adc2772d..be316523699 100644 --- a/backend/internal/repository/usage_billing_repo.go +++ b/backend/internal/repository/usage_billing_repo.go @@ -213,31 +213,7 @@ func (r *usageBillingRepository) applyUsageBillingEffects(ctx context.Context, t } func incrementUsageBillingSubscription(ctx context.Context, tx *sql.Tx, subscriptionID int64, costUSD float64) error { - const updateSQL = ` - UPDATE user_subscriptions us - SET - daily_usage_usd = us.daily_usage_usd + $1, - weekly_usage_usd = us.weekly_usage_usd + $1, - monthly_usage_usd = us.monthly_usage_usd + $1, - updated_at = NOW() - FROM groups g - WHERE us.id = $2 - AND us.deleted_at IS NULL - AND us.group_id = g.id - AND g.deleted_at IS NULL - ` - res, err := tx.ExecContext(ctx, updateSQL, costUSD, subscriptionID) - if err != nil { - return err - } - affected, err := res.RowsAffected() - if err != nil { - return err - } - if affected > 0 { - return nil - } - return service.ErrSubscriptionNotFound + return incrementSubscriptionUsage(ctx, tx, subscriptionID, costUSD) } func deductUsageBillingBalance(ctx context.Context, tx *sql.Tx, userID int64, amount float64) (float64, bool, error) { diff --git a/backend/internal/repository/usage_billing_repo_integration_test.go b/backend/internal/repository/usage_billing_repo_integration_test.go index e8d4d32707f..4d6922e41f7 100644 --- a/backend/internal/repository/usage_billing_repo_integration_test.go +++ b/backend/internal/repository/usage_billing_repo_integration_test.go @@ -128,6 +128,224 @@ func TestUsageBillingRepositoryApply_DeduplicatesSubscriptionBilling(t *testing. require.InDelta(t, 2.5, dailyUsage, 0.000001) } +func TestUsageBillingRepositoryApply_TotalQuotaSubscriptionFIFO(t *testing.T) { + ctx := context.Background() + client := testEntClient(t) + repo := NewUsageBillingRepository(client, integrationDB) + + totalLimit := 100.0 + user := mustCreateUser(t, client, &service.User{ + Email: fmt.Sprintf("usage-billing-total-user-%d@example.com", time.Now().UnixNano()), + PasswordHash: "hash", + }) + group := mustCreateGroup(t, client, &service.Group{ + Name: "usage-billing-total-group-" + uuid.NewString(), + Platform: service.PlatformAnthropic, + SubscriptionType: service.SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }) + apiKey := mustCreateApiKey(t, client, &service.APIKey{ + UserID: user.ID, + GroupID: &group.ID, + Key: "sk-usage-billing-total-" + uuid.NewString(), + Name: "billing-total", + }) + subscription := mustCreateSubscription(t, client, &service.UserSubscription{ + UserID: user.ID, + GroupID: group.ID, + }) + + now := time.Now().UTC() + _, err := client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(subscription.ID). + SetQuotaTotalUsd(100). + SetQuotaUsedUsd(0). + SetStartsAt(now). + SetExpiresAt(now.Add(time.Hour)). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(ctx) + require.NoError(t, err) + _, err = client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(subscription.ID). + SetQuotaTotalUsd(100). + SetQuotaUsedUsd(0). + SetStartsAt(now). + SetExpiresAt(now.Add(2 * time.Hour)). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(ctx) + require.NoError(t, err) + + _, err = repo.Apply(ctx, &service.UsageBillingCommand{ + RequestID: uuid.NewString(), + APIKeyID: apiKey.ID, + UserID: user.ID, + AccountID: 0, + SubscriptionID: &subscription.ID, + SubscriptionCost: 130, + }) + require.NoError(t, err) + + rows, err := integrationDB.QueryContext(ctx, ` + SELECT quota_used_usd + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + ORDER BY expires_at ASC, id ASC + `, subscription.ID) + require.NoError(t, err) + defer func() { _ = rows.Close() }() + + var used []float64 + for rows.Next() { + var quotaUsed float64 + require.NoError(t, rows.Scan("aUsed)) + used = append(used, quotaUsed) + } + require.NoError(t, rows.Err()) + require.Equal(t, []float64{100, 30}, used) +} + +func TestUsageBillingRepositoryApply_TotalQuotaSubscriptionAllowsOverflow(t *testing.T) { + ctx := context.Background() + client := testEntClient(t) + repo := NewUsageBillingRepository(client, integrationDB) + + totalLimit := 50.0 + user := mustCreateUser(t, client, &service.User{ + Email: fmt.Sprintf("usage-billing-total-overflow-user-%d@example.com", time.Now().UnixNano()), + PasswordHash: "hash", + }) + group := mustCreateGroup(t, client, &service.Group{ + Name: "usage-billing-total-overflow-group-" + uuid.NewString(), + Platform: service.PlatformAnthropic, + SubscriptionType: service.SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }) + apiKey := mustCreateApiKey(t, client, &service.APIKey{ + UserID: user.ID, + GroupID: &group.ID, + Key: "sk-usage-billing-total-overflow-" + uuid.NewString(), + Name: "billing-total-overflow", + }) + subscription := mustCreateSubscription(t, client, &service.UserSubscription{ + UserID: user.ID, + GroupID: group.ID, + }) + + now := time.Now().UTC() + event, err := client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(subscription.ID). + SetQuotaTotalUsd(50). + SetQuotaUsedUsd(0). + SetStartsAt(now). + SetExpiresAt(now.Add(time.Hour)). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(ctx) + require.NoError(t, err) + + _, err = repo.Apply(ctx, &service.UsageBillingCommand{ + RequestID: uuid.NewString(), + APIKeyID: apiKey.ID, + UserID: user.ID, + AccountID: 0, + SubscriptionID: &subscription.ID, + SubscriptionCost: 60, + }) + require.NoError(t, err) + + var quotaUsed float64 + require.NoError(t, integrationDB.QueryRowContext(ctx, ` + SELECT quota_used_usd + FROM user_subscription_quota_events + WHERE id = $1 + `, event.ID).Scan("aUsed)) + require.InDelta(t, 60, quotaUsed, 0.000001) +} + +func TestUsageBillingRepositoryApply_TotalQuotaSubscriptionUsesAdmissionSnapshotAfterExpiry(t *testing.T) { + ctx := context.Background() + client := testEntClient(t) + repo := NewUsageBillingRepository(client, integrationDB) + + totalLimit := 100.0 + user := mustCreateUser(t, client, &service.User{ + Email: fmt.Sprintf("usage-billing-total-snapshot-user-%d@example.com", time.Now().UnixNano()), + PasswordHash: "hash", + }) + group := mustCreateGroup(t, client, &service.Group{ + Name: "usage-billing-total-snapshot-group-" + uuid.NewString(), + Platform: service.PlatformAnthropic, + SubscriptionType: service.SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }) + apiKey := mustCreateApiKey(t, client, &service.APIKey{ + UserID: user.ID, + GroupID: &group.ID, + Key: "sk-usage-billing-total-snapshot-" + uuid.NewString(), + Name: "billing-total-snapshot", + }) + subscription := mustCreateSubscription(t, client, &service.UserSubscription{ + UserID: user.ID, + GroupID: group.ID, + }) + + now := time.Now().UTC() + event1, err := client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(subscription.ID). + SetQuotaTotalUsd(100). + SetQuotaUsedUsd(0). + SetStartsAt(now.Add(-2 * time.Hour)). + SetExpiresAt(now.Add(-time.Minute)). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(ctx) + require.NoError(t, err) + event2, err := client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(subscription.ID). + SetQuotaTotalUsd(100). + SetQuotaUsedUsd(0). + SetStartsAt(now.Add(-time.Hour)). + SetExpiresAt(now.Add(time.Hour)). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(ctx) + require.NoError(t, err) + + snapshotCtx := service.WithTotalQuotaSpendSnapshot(ctx, &service.TotalQuotaSpendSnapshot{ + SubscriptionID: subscription.ID, + EventIDs: []int64{event1.ID, event2.ID}, + OverflowEventID: event2.ID, + TakenAt: now.Add(-2 * time.Minute), + }) + + _, err = repo.Apply(snapshotCtx, &service.UsageBillingCommand{ + RequestID: uuid.NewString(), + APIKeyID: apiKey.ID, + UserID: user.ID, + AccountID: 0, + SubscriptionID: &subscription.ID, + SubscriptionCost: 120, + }) + require.NoError(t, err) + + rows, err := integrationDB.QueryContext(ctx, ` + SELECT id, quota_used_usd + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + ORDER BY id ASC + `, subscription.ID) + require.NoError(t, err) + defer func() { _ = rows.Close() }() + + usedByID := make(map[int64]float64) + for rows.Next() { + var id int64 + var quotaUsed float64 + require.NoError(t, rows.Scan(&id, "aUsed)) + usedByID[id] = quotaUsed + } + require.NoError(t, rows.Err()) + require.InDelta(t, 100, usedByID[event1.ID], 0.000001) + require.InDelta(t, 20, usedByID[event2.ID], 0.000001) +} + func TestUsageBillingRepositoryApply_RequestFingerprintConflict(t *testing.T) { ctx := context.Background() client := testEntClient(t) diff --git a/backend/internal/repository/user_subscription_quota_usage.go b/backend/internal/repository/user_subscription_quota_usage.go new file mode 100644 index 00000000000..848775a7332 --- /dev/null +++ b/backend/internal/repository/user_subscription_quota_usage.go @@ -0,0 +1,199 @@ +package repository + +import ( + "context" + "database/sql" + "math" + "time" + + "github.com/lib/pq" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +const totalQuotaConsumeEpsilon = 1e-9 + +func incrementSubscriptionUsage(ctx context.Context, exec sqlExecutor, subscriptionID int64, costUSD float64) error { + if costUSD <= 0 { + return nil + } + + subscriptionType, err := getSubscriptionTypeForUsage(ctx, exec, subscriptionID) + if err != nil { + return err + } + if subscriptionType == service.SubscriptionTypeTotalQuota { + return consumeTotalQuotaSubscriptionUsage(ctx, exec, subscriptionID, costUSD) + } + return incrementWindowedSubscriptionUsage(ctx, exec, subscriptionID, costUSD) +} + +func getSubscriptionTypeForUsage(ctx context.Context, exec sqlExecutor, subscriptionID int64) (string, error) { + var subscriptionType string + err := scanSingleRow(ctx, exec, ` + SELECT g.subscription_type + FROM user_subscriptions us + JOIN groups g ON g.id = us.group_id + WHERE us.id = $1 + AND us.deleted_at IS NULL + AND g.deleted_at IS NULL + `, []any{subscriptionID}, &subscriptionType) + if err == sql.ErrNoRows { + return "", service.ErrSubscriptionNotFound + } + if err != nil { + return "", err + } + return subscriptionType, nil +} + +func incrementWindowedSubscriptionUsage(ctx context.Context, exec sqlExecutor, subscriptionID int64, costUSD float64) error { + const updateSQL = ` + UPDATE user_subscriptions us + SET + daily_usage_usd = us.daily_usage_usd + $1, + weekly_usage_usd = us.weekly_usage_usd + $1, + monthly_usage_usd = us.monthly_usage_usd + $1, + updated_at = NOW() + FROM groups g + WHERE us.id = $2 + AND us.deleted_at IS NULL + AND us.group_id = g.id + AND g.deleted_at IS NULL + ` + res, err := exec.ExecContext(ctx, updateSQL, costUSD, subscriptionID) + if err != nil { + return err + } + affected, err := res.RowsAffected() + if err != nil { + return err + } + if affected > 0 { + return nil + } + return service.ErrSubscriptionNotFound +} + +func consumeTotalQuotaSubscriptionUsage(ctx context.Context, exec sqlExecutor, subscriptionID int64, costUSD float64) error { + events, overflowTargetID, err := loadTotalQuotaSpendRows(ctx, exec, subscriptionID) + if err != nil { + return err + } + if len(events) == 0 || overflowTargetID == 0 { + return service.ErrTotalLimitExceeded + } + + remainingCost := costUSD + for _, event := range events { + remaining := event.totalUSD - event.usedUSD + if remaining <= totalQuotaConsumeEpsilon { + continue + } + delta := math.Min(remainingCost, remaining) + if delta <= totalQuotaConsumeEpsilon { + break + } + if _, err := exec.ExecContext(ctx, ` + UPDATE user_subscription_quota_events + SET quota_used_usd = quota_used_usd + $1, + updated_at = NOW() + WHERE id = $2 + `, delta, event.id); err != nil { + return err + } + remainingCost -= delta + if remainingCost <= totalQuotaConsumeEpsilon { + return nil + } + } + + if remainingCost > totalQuotaConsumeEpsilon { + if _, err := exec.ExecContext(ctx, ` + UPDATE user_subscription_quota_events + SET quota_used_usd = quota_used_usd + $1, + updated_at = NOW() + WHERE id = $2 + `, remainingCost, overflowTargetID); err != nil { + return err + } + } + return nil +} + +type totalQuotaSpendRow struct { + id int64 + totalUSD float64 + usedUSD float64 +} + +func loadTotalQuotaSpendRows(ctx context.Context, exec sqlExecutor, subscriptionID int64) ([]totalQuotaSpendRow, int64, error) { + if snapshot, ok := service.TotalQuotaSpendSnapshotFromContext(ctx); ok && + snapshot != nil && + snapshot.SubscriptionID == subscriptionID && + len(snapshot.EventIDs) > 0 { + rows, err := exec.QueryContext(ctx, ` + WITH snapshot_ids AS ( + SELECT id, ord + FROM unnest($2::bigint[]) WITH ORDINALITY AS snapshot_ids(id, ord) + ) + SELECT e.id, e.quota_total_usd, e.quota_used_usd + FROM snapshot_ids s + JOIN user_subscription_quota_events e ON e.id = s.id + WHERE e.user_subscription_id = $1 + ORDER BY s.ord + FOR UPDATE OF e + `, subscriptionID, pq.Array(snapshot.EventIDs)) + if err != nil { + return nil, 0, err + } + defer func() { _ = rows.Close() }() + + events := make([]totalQuotaSpendRow, 0, len(snapshot.EventIDs)) + for rows.Next() { + var row totalQuotaSpendRow + if err := rows.Scan(&row.id, &row.totalUSD, &row.usedUSD); err != nil { + return nil, 0, err + } + events = append(events, row) + } + if err := rows.Err(); err != nil { + return nil, 0, err + } + overflowTargetID := snapshot.OverflowEventID + if overflowTargetID == 0 && len(events) > 0 { + overflowTargetID = events[len(events)-1].id + } + return events, overflowTargetID, nil + } + + now := time.Now().UTC() + rows, err := exec.QueryContext(ctx, ` + SELECT id, quota_total_usd, quota_used_usd + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + AND expires_at > $2 + ORDER BY expires_at ASC, id ASC + FOR UPDATE + `, subscriptionID, now) + if err != nil { + return nil, 0, err + } + defer func() { _ = rows.Close() }() + + events := make([]totalQuotaSpendRow, 0) + for rows.Next() { + var row totalQuotaSpendRow + if err := rows.Scan(&row.id, &row.totalUSD, &row.usedUSD); err != nil { + return nil, 0, err + } + events = append(events, row) + } + if err := rows.Err(); err != nil { + return nil, 0, err + } + overflowTargetID := int64(0) + if len(events) > 0 { + overflowTargetID = events[len(events)-1].id + } + return events, overflowTargetID, nil +} diff --git a/backend/internal/repository/user_subscription_repo.go b/backend/internal/repository/user_subscription_repo.go index 37f06a038b7..207aeb34ddd 100644 --- a/backend/internal/repository/user_subscription_repo.go +++ b/backend/internal/repository/user_subscription_repo.go @@ -2,6 +2,8 @@ package repository import ( "context" + "database/sql" + "strings" "time" dbent "github.com/Wei-Shaw/sub2api/ent" @@ -12,6 +14,7 @@ import ( "github.com/Wei-Shaw/sub2api/ent/usersubscription" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/Wei-Shaw/sub2api/internal/service" + "github.com/lib/pq" ) type userSubscriptionRepository struct { @@ -72,7 +75,18 @@ func (r *userSubscriptionRepository) GetByID(ctx context.Context, id int64) (*se if err != nil { return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil) } - return userSubscriptionEntityToService(m), nil + sub := userSubscriptionEntityToService(m) + if sub == nil { + return nil, service.ErrSubscriptionNotFound + } + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + summary, err := r.GetQuotaSummary(ctx, sub.ID, time.Now()) + if err != nil { + return nil, err + } + applyQuotaSummaryToSubscription(sub, summary) + } + return sub, nil } func (r *userSubscriptionRepository) GetByIDIncludeDeleted(ctx context.Context, id int64) (*service.UserSubscription, error) { @@ -99,7 +113,18 @@ func (r *userSubscriptionRepository) GetByUserIDAndGroupID(ctx context.Context, if err != nil { return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil) } - return userSubscriptionEntityToService(m), nil + sub := userSubscriptionEntityToService(m) + if sub == nil { + return nil, service.ErrSubscriptionNotFound + } + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + summary, err := r.GetQuotaSummary(ctx, sub.ID, time.Now()) + if err != nil { + return nil, err + } + applyQuotaSummaryToSubscription(sub, summary) + } + return sub, nil } func (r *userSubscriptionRepository) GetActiveByUserIDAndGroupID(ctx context.Context, userID, groupID int64) (*service.UserSubscription, error) { @@ -116,7 +141,18 @@ func (r *userSubscriptionRepository) GetActiveByUserIDAndGroupID(ctx context.Con if err != nil { return nil, translatePersistenceError(err, service.ErrSubscriptionNotFound, nil) } - return userSubscriptionEntityToService(m), nil + sub := userSubscriptionEntityToService(m) + if sub == nil { + return nil, service.ErrSubscriptionNotFound + } + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + summary, err := r.GetQuotaSummary(ctx, sub.ID, time.Now()) + if err != nil { + return nil, err + } + applyQuotaSummaryToSubscription(sub, summary) + } + return sub, nil } func (r *userSubscriptionRepository) Update(ctx context.Context, sub *service.UserSubscription) error { @@ -180,7 +216,11 @@ func (r *userSubscriptionRepository) ListByUserID(ctx context.Context, userID in if err != nil { return nil, err } - return userSubscriptionEntitiesToService(subs), nil + out := userSubscriptionEntitiesToService(subs) + if err := r.populateQuotaSummaries(ctx, out); err != nil { + return nil, err + } + return out, nil } func (r *userSubscriptionRepository) ListActiveByUserID(ctx context.Context, userID int64) ([]service.UserSubscription, error) { @@ -197,7 +237,11 @@ func (r *userSubscriptionRepository) ListActiveByUserID(ctx context.Context, use if err != nil { return nil, err } - return userSubscriptionEntitiesToService(subs), nil + out := userSubscriptionEntitiesToService(subs) + if err := r.populateQuotaSummaries(ctx, out); err != nil { + return nil, err + } + return out, nil } func (r *userSubscriptionRepository) ListByGroupID(ctx context.Context, groupID int64, params pagination.PaginationParams) ([]service.UserSubscription, *pagination.PaginationResult, error) { @@ -220,7 +264,11 @@ func (r *userSubscriptionRepository) ListByGroupID(ctx context.Context, groupID return nil, nil, err } - return userSubscriptionEntitiesToService(subs), paginationResultFromTotal(int64(total), params), nil + out := userSubscriptionEntitiesToService(subs) + if err := r.populateQuotaSummaries(ctx, out); err != nil { + return nil, nil, err + } + return out, paginationResultFromTotal(int64(total), params), nil } func (r *userSubscriptionRepository) List(ctx context.Context, params pagination.PaginationParams, userID, groupID *int64, status, platform, sortBy, sortOrder string) ([]service.UserSubscription, *pagination.PaginationResult, error) { @@ -317,7 +365,9 @@ func (r *userSubscriptionRepository) List(ctx context.Context, params pagination return nil, nil, err } } - + if err := r.populateQuotaSummaries(ctx, result); err != nil { + return nil, nil, err + } return result, paginationResultFromTotal(int64(total), params), nil } @@ -451,37 +501,237 @@ func (r *userSubscriptionRepository) translateConditionalWindowReset(ctx context // 限额检查已在请求前由 BillingCacheService.CheckBillingEligibility 完成, // 此处仅负责记录实际消费,确保消费数据的完整性。 func (r *userSubscriptionRepository) IncrementUsage(ctx context.Context, id int64, costUSD float64) error { - const updateSQL = ` - UPDATE user_subscriptions us - SET - daily_usage_usd = us.daily_usage_usd + $1, - weekly_usage_usd = us.weekly_usage_usd + $1, - monthly_usage_usd = us.monthly_usage_usd + $1, - updated_at = NOW() - FROM groups g - WHERE us.id = $2 - AND us.deleted_at IS NULL - AND us.group_id = g.id - AND g.deleted_at IS NULL - ` + client := clientFromContext(ctx, r.client) + return incrementSubscriptionUsage(ctx, client, id, costUSD) +} +func (r *userSubscriptionRepository) CreateQuotaEvent(ctx context.Context, event *service.UserSubscriptionQuotaEvent) error { + if event == nil { + return service.ErrSubscriptionNilInput + } client := clientFromContext(ctx, r.client) - result, err := client.ExecContext(ctx, updateSQL, costUSD, id) + builder := client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(event.UserSubscriptionID). + SetQuotaTotalUsd(event.QuotaTotalUSD). + SetQuotaUsedUsd(event.QuotaUsedUSD). + SetStartsAt(event.StartsAt). + SetExpiresAt(event.ExpiresAt). + SetSourceKind(event.SourceKind) + if strings.TrimSpace(event.SourceRef) != "" { + builder.SetSourceRef(event.SourceRef) + } + created, err := builder.Save(ctx) if err != nil { return err } + event.ID = created.ID + event.CreatedAt = created.CreatedAt + event.UpdatedAt = created.UpdatedAt + return nil +} - affected, err := result.RowsAffected() +func (r *userSubscriptionRepository) RetireDepletedQuotaEventsOnAppend(ctx context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error { + if subscriptionID <= 0 { + return service.ErrSubscriptionNotFound + } + + retireAt = retireAt.UTC() + exec := clientFromContext(ctx, r.client) + + if _, err := exec.ExecContext(ctx, ` + UPDATE user_subscription_quota_events + SET expires_at = $3, + updated_at = NOW() + WHERE user_subscription_id = $1 + AND id <> $2 + AND expires_at > $3 + AND quota_total_usd - quota_used_usd <= $4 + `, subscriptionID, keepEventID, retireAt, totalQuotaConsumeEpsilon); err != nil { + return err + } + + var maxActiveExpiry time.Time + if err := scanSingleRow(ctx, exec, ` + SELECT COALESCE(MAX(expires_at), $2) + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + AND expires_at > $2 + `, []any{subscriptionID, retireAt}, &maxActiveExpiry); err != nil { + return err + } + + res, err := exec.ExecContext(ctx, ` + UPDATE user_subscriptions + SET expires_at = $2, + updated_at = NOW() + WHERE id = $1 + AND deleted_at IS NULL + `, subscriptionID, maxActiveExpiry) + if err != nil { + return err + } + affected, err := res.RowsAffected() if err != nil { return err } + if affected == 0 { + return service.ErrSubscriptionNotFound + } + return nil +} - if affected > 0 { - return nil +func (r *userSubscriptionRepository) GetQuotaSummary(ctx context.Context, subscriptionID int64, now time.Time) (*service.UserSubscriptionQuotaSummary, error) { + summaries, err := r.GetQuotaSummaryBatch(ctx, []int64{subscriptionID}, now) + if err != nil { + return nil, err + } + if summary, ok := summaries[subscriptionID]; ok { + return summary, nil + } + return &service.UserSubscriptionQuotaSummary{}, nil +} + +func (r *userSubscriptionRepository) GetQuotaSummaryBatch(ctx context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*service.UserSubscriptionQuotaSummary, error) { + result := make(map[int64]*service.UserSubscriptionQuotaSummary) + if r == nil || len(subscriptionIDs) == 0 { + return result, nil } - // affected == 0:订阅不存在或已删除 - return service.ErrSubscriptionNotFound + normalized := make([]int64, 0, len(subscriptionIDs)) + seen := make(map[int64]struct{}, len(subscriptionIDs)) + for _, id := range subscriptionIDs { + if id <= 0 { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + normalized = append(normalized, id) + } + if len(normalized) == 0 { + return result, nil + } + + exec := clientFromContext(ctx, r.client) + rows, err := exec.QueryContext(ctx, ` + WITH active AS ( + SELECT + user_subscription_id, + quota_total_usd, + quota_used_usd, + expires_at, + GREATEST(quota_total_usd - quota_used_usd, 0) AS remaining + FROM user_subscription_quota_events + WHERE user_subscription_id = ANY($1) + AND expires_at > $2 + ), + totals AS ( + SELECT + user_subscription_id, + COALESCE(SUM(quota_total_usd), 0) AS total_limit_usd, + COALESCE(SUM(quota_used_usd), 0) AS total_used_usd, + COALESCE(SUM(remaining), 0) AS total_remaining_usd, + MIN(CASE WHEN remaining > 0 THEN expires_at END) AS next_quota_expire_at + FROM active + GROUP BY user_subscription_id + ) + SELECT + t.user_subscription_id, + t.total_limit_usd, + t.total_used_usd, + t.total_remaining_usd, + t.next_quota_expire_at, + COALESCE(SUM(a.remaining) FILTER ( + WHERE a.remaining > 0 AND a.expires_at = t.next_quota_expire_at + ), 0) AS next_expiring_quota_usd + FROM totals t + LEFT JOIN active a ON a.user_subscription_id = t.user_subscription_id + GROUP BY + t.user_subscription_id, + t.total_limit_usd, + t.total_used_usd, + t.total_remaining_usd, + t.next_quota_expire_at + `, pq.Array(normalized), now.UTC()) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + for rows.Next() { + var ( + subscriptionID int64 + totalLimitUSD float64 + totalUsedUSD float64 + totalRemainingUSD float64 + nextQuotaExpireAt sql.NullTime + nextExpiringQuotaUSD float64 + ) + if err := rows.Scan( + &subscriptionID, + &totalLimitUSD, + &totalUsedUSD, + &totalRemainingUSD, + &nextQuotaExpireAt, + &nextExpiringQuotaUSD, + ); err != nil { + return nil, err + } + summary := &service.UserSubscriptionQuotaSummary{ + TotalLimitUSD: totalLimitUSD, + TotalUsedUSD: totalUsedUSD, + TotalRemainingUSD: totalRemainingUSD, + NextExpiringQuotaUSD: nextExpiringQuotaUSD, + } + if nextQuotaExpireAt.Valid { + t := nextQuotaExpireAt.Time + summary.NextQuotaExpireAt = &t + } + result[subscriptionID] = summary + } + if err := rows.Err(); err != nil { + return nil, err + } + + return result, nil +} + +func (r *userSubscriptionRepository) GetQuotaSpendSnapshot(ctx context.Context, subscriptionID int64, now time.Time) (*service.TotalQuotaSpendSnapshot, error) { + exec := clientFromContext(ctx, r.client) + rows, err := exec.QueryContext(ctx, ` + SELECT id + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + AND expires_at > $2 + ORDER BY expires_at ASC, id ASC + `, subscriptionID, now.UTC()) + if err != nil { + return nil, err + } + defer func() { _ = rows.Close() }() + + eventIDs := make([]int64, 0) + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + eventIDs = append(eventIDs, id) + } + if err := rows.Err(); err != nil { + return nil, err + } + if len(eventIDs) == 0 { + return nil, nil + } + + return &service.TotalQuotaSpendSnapshot{ + SubscriptionID: subscriptionID, + EventIDs: eventIDs, + OverflowEventID: eventIDs[len(eventIDs)-1], + TakenAt: now.UTC(), + }, nil } func (r *userSubscriptionRepository) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) { @@ -496,6 +746,35 @@ func (r *userSubscriptionRepository) BatchUpdateExpiredStatus(ctx context.Contex return int64(n), err } +func (r *userSubscriptionRepository) DeleteExpiredQuotaEventsBatch(ctx context.Context, now time.Time, limit int) (int64, error) { + if limit <= 0 { + return 0, nil + } + + exec := clientFromContext(ctx, r.client) + res, err := exec.ExecContext(ctx, ` + WITH targets AS ( + SELECT id + FROM user_subscription_quota_events + WHERE expires_at <= $1 + ORDER BY expires_at ASC, id ASC + FOR UPDATE SKIP LOCKED + LIMIT $2 + ) + DELETE FROM user_subscription_quota_events e + USING targets t + WHERE e.id = t.id + `, now.UTC(), limit) + if err != nil { + return 0, err + } + affected, err := res.RowsAffected() + if err != nil { + return 0, err + } + return affected, nil +} + // Extra repository helpers (currently used only by integration tests). func (r *userSubscriptionRepository) ListExpired(ctx context.Context) ([]service.UserSubscription, error) { @@ -651,6 +930,12 @@ func userSubscriptionEntityToServiceWithStatusMapping(m *dbent.UserSubscription, if m.Edges.AssignedByUser != nil { out.AssignedByUser = userEntityToService(m.Edges.AssignedByUser) } + if len(m.Edges.QuotaEvents) > 0 { + out.QuotaEvents = make([]service.UserSubscriptionQuotaEvent, 0, len(m.Edges.QuotaEvents)) + for i := range m.Edges.QuotaEvents { + out.QuotaEvents = append(out.QuotaEvents, quotaEventEntityToService(m.Edges.QuotaEvents[i])) + } + } return out } @@ -672,3 +957,64 @@ func applyUserSubscriptionEntityToService(dst *service.UserSubscription, src *db dst.CreatedAt = src.CreatedAt dst.UpdatedAt = src.UpdatedAt } + +func quotaEventEntityToService(m *dbent.UserSubscriptionQuotaEvent) service.UserSubscriptionQuotaEvent { + return service.UserSubscriptionQuotaEvent{ + ID: m.ID, + UserSubscriptionID: m.UserSubscriptionID, + QuotaTotalUSD: m.QuotaTotalUsd, + QuotaUsedUSD: m.QuotaUsedUsd, + StartsAt: m.StartsAt, + ExpiresAt: m.ExpiresAt, + SourceKind: m.SourceKind, + SourceRef: derefString(m.SourceRef), + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + } +} + +func applyQuotaSummaryToSubscription(sub *service.UserSubscription, summary *service.UserSubscriptionQuotaSummary) { + if sub == nil { + return + } + sub.TotalLimitUSD = 0 + sub.TotalUsedUSD = 0 + sub.TotalRemainingUSD = 0 + sub.NextQuotaExpireAt = nil + sub.NextExpiringQuotaUSD = 0 + if summary == nil { + return + } + sub.TotalLimitUSD = summary.TotalLimitUSD + sub.TotalUsedUSD = summary.TotalUsedUSD + sub.TotalRemainingUSD = summary.TotalRemainingUSD + sub.NextQuotaExpireAt = summary.NextQuotaExpireAt + sub.NextExpiringQuotaUSD = summary.NextExpiringQuotaUSD +} + +func (r *userSubscriptionRepository) populateQuotaSummaries(ctx context.Context, subs []service.UserSubscription) error { + if len(subs) == 0 { + return nil + } + + ids := make([]int64, 0, len(subs)) + for i := range subs { + if subs[i].Group != nil && subs[i].Group.IsTotalQuotaSubscriptionType() { + ids = append(ids, subs[i].ID) + } + } + if len(ids) == 0 { + return nil + } + + summaries, err := r.GetQuotaSummaryBatch(ctx, ids, time.Now()) + if err != nil { + return err + } + for i := range subs { + if subs[i].Group != nil && subs[i].Group.IsTotalQuotaSubscriptionType() { + applyQuotaSummaryToSubscription(&subs[i], summaries[subs[i].ID]) + } + } + return nil +} diff --git a/backend/internal/repository/user_subscription_repo_integration_test.go b/backend/internal/repository/user_subscription_repo_integration_test.go index 96eead494e6..14cb31ea304 100644 --- a/backend/internal/repository/user_subscription_repo_integration_test.go +++ b/backend/internal/repository/user_subscription_repo_integration_test.go @@ -646,6 +646,181 @@ func (s *UserSubscriptionRepoSuite) TestBatchUpdateExpiredStatus() { s.Require().Equal(service.SubscriptionStatusExpired, gotExpired.Status) } +func (s *UserSubscriptionRepoSuite) TestRetireDepletedQuotaEventsOnAppend_RetiresDepletedAndSyncsExpiry() { + user := s.mustCreateUser("quota-retire@test.com", service.RoleUser) + totalLimit := 1.0 + group, err := s.client.Group.Create(). + SetName("g-quota-retire"). + SetStatus(service.StatusActive). + SetSubscriptionType(service.SubscriptionTypeTotalQuota). + SetTotalLimitUsd(totalLimit). + Save(s.ctx) + s.Require().NoError(err, "create total quota group") + + sub := s.mustCreateSubscription(user.ID, group.ID, func(c *dbent.UserSubscriptionCreate) { + c.SetExpiresAt(time.Now().UTC().Add(72 * time.Hour)) + }) + retireAt := time.Now().UTC().Truncate(time.Microsecond) + oldExpiry := retireAt.Add(48 * time.Hour) + newExpiry := retireAt.Add(24 * time.Hour) + + var retiredEventID int64 + err = scanSingleRow(s.ctx, s.client, ` + INSERT INTO user_subscription_quota_events ( + user_subscription_id, quota_total_usd, quota_used_usd, starts_at, expires_at, source_kind + ) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + `, []any{sub.ID, 1.0, 1.5, retireAt.Add(-time.Hour), oldExpiry, service.SubscriptionQuotaSourceRedeemCode}, &retiredEventID) + s.Require().NoError(err, "insert depleted event") + + var keepEventID int64 + err = scanSingleRow(s.ctx, s.client, ` + INSERT INTO user_subscription_quota_events ( + user_subscription_id, quota_total_usd, quota_used_usd, starts_at, expires_at, source_kind + ) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + `, []any{sub.ID, 1.0, 0.0, retireAt, newExpiry, service.SubscriptionQuotaSourceAdminAssign}, &keepEventID) + s.Require().NoError(err, "insert kept event") + + err = s.repo.RetireDepletedQuotaEventsOnAppend(s.ctx, sub.ID, keepEventID, retireAt) + s.Require().NoError(err, "RetireDepletedQuotaEventsOnAppend") + + summary, err := s.repo.GetQuotaSummary(s.ctx, sub.ID, retireAt.Add(time.Second)) + s.Require().NoError(err, "GetQuotaSummary") + s.Require().InDelta(1.0, summary.TotalLimitUSD, 1e-6) + s.Require().InDelta(0.0, summary.TotalUsedUSD, 1e-6) + s.Require().InDelta(1.0, summary.TotalRemainingUSD, 1e-6) + + var retiredExpiresAt time.Time + err = scanSingleRow(s.ctx, s.client, ` + SELECT expires_at + FROM user_subscription_quota_events + WHERE id = $1 + `, []any{retiredEventID}, &retiredExpiresAt) + s.Require().NoError(err, "query retired event") + s.Require().False(retiredExpiresAt.After(retireAt), "retired event should leave the active set immediately") + + gotSub, err := s.repo.GetByID(s.ctx, sub.ID) + s.Require().NoError(err, "GetByID") + s.Require().WithinDuration(newExpiry, gotSub.ExpiresAt, time.Second, "subscription expiry should match the latest active event") +} + +func (s *UserSubscriptionRepoSuite) TestRetireDepletedQuotaEventsOnAppend_KeepsPartiallyUsedEventsActive() { + user := s.mustCreateUser("quota-partial@test.com", service.RoleUser) + totalLimit := 1.0 + group, err := s.client.Group.Create(). + SetName("g-quota-partial"). + SetStatus(service.StatusActive). + SetSubscriptionType(service.SubscriptionTypeTotalQuota). + SetTotalLimitUsd(totalLimit). + Save(s.ctx) + s.Require().NoError(err, "create total quota group") + + sub := s.mustCreateSubscription(user.ID, group.ID, func(c *dbent.UserSubscriptionCreate) { + c.SetExpiresAt(time.Now().UTC().Add(72 * time.Hour)) + }) + now := time.Now().UTC().Truncate(time.Microsecond) + oldExpiry := now.Add(48 * time.Hour) + newExpiry := now.Add(24 * time.Hour) + + var oldEventID int64 + err = scanSingleRow(s.ctx, s.client, ` + INSERT INTO user_subscription_quota_events ( + user_subscription_id, quota_total_usd, quota_used_usd, starts_at, expires_at, source_kind + ) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + `, []any{sub.ID, 1.0, 0.5, now.Add(-time.Hour), oldExpiry, service.SubscriptionQuotaSourceRedeemCode}, &oldEventID) + s.Require().NoError(err, "insert partially used event") + + var keepEventID int64 + err = scanSingleRow(s.ctx, s.client, ` + INSERT INTO user_subscription_quota_events ( + user_subscription_id, quota_total_usd, quota_used_usd, starts_at, expires_at, source_kind + ) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id + `, []any{sub.ID, 1.0, 0.0, now, newExpiry, service.SubscriptionQuotaSourceAdminAssign}, &keepEventID) + s.Require().NoError(err, "insert kept event") + + err = s.repo.RetireDepletedQuotaEventsOnAppend(s.ctx, sub.ID, keepEventID, now) + s.Require().NoError(err, "RetireDepletedQuotaEventsOnAppend") + + summary, err := s.repo.GetQuotaSummary(s.ctx, sub.ID, now.Add(time.Second)) + s.Require().NoError(err, "GetQuotaSummary") + s.Require().InDelta(2.0, summary.TotalLimitUSD, 1e-6) + s.Require().InDelta(0.5, summary.TotalUsedUSD, 1e-6) + s.Require().InDelta(1.5, summary.TotalRemainingUSD, 1e-6) + + var oldExpiresAt time.Time + err = scanSingleRow(s.ctx, s.client, ` + SELECT expires_at + FROM user_subscription_quota_events + WHERE id = $1 + `, []any{oldEventID}, &oldExpiresAt) + s.Require().NoError(err, "query partially used event") + s.Require().WithinDuration(oldExpiry, oldExpiresAt, time.Second, "partially used event should stay active") + + gotSub, err := s.repo.GetByID(s.ctx, sub.ID) + s.Require().NoError(err, "GetByID") + s.Require().WithinDuration(oldExpiry, gotSub.ExpiresAt, time.Second, "subscription expiry should remain at the latest active event") +} + +func (s *UserSubscriptionRepoSuite) TestDeleteExpiredQuotaEventsBatch() { + user := s.mustCreateUser("quota-cleanup@test.com", service.RoleUser) + totalLimit := 1.0 + group, err := s.client.Group.Create(). + SetName("g-quota-cleanup"). + SetStatus(service.StatusActive). + SetSubscriptionType(service.SubscriptionTypeTotalQuota). + SetTotalLimitUsd(totalLimit). + Save(s.ctx) + s.Require().NoError(err, "create total quota group") + + sub := s.mustCreateSubscription(user.ID, group.ID, nil) + now := time.Now().UTC().Truncate(time.Microsecond) + + for _, expiresAt := range []time.Time{ + now.Add(-3 * time.Hour), + now.Add(-2 * time.Hour), + now.Add(-1 * time.Hour), + now.Add(1 * time.Hour), + } { + _, err := s.client.UserSubscriptionQuotaEvent.Create(). + SetUserSubscriptionID(sub.ID). + SetQuotaTotalUsd(1). + SetQuotaUsedUsd(0). + SetStartsAt(now.Add(-4 * time.Hour)). + SetExpiresAt(expiresAt). + SetSourceKind(service.SubscriptionQuotaSourceAdminAssign). + Save(s.ctx) + s.Require().NoError(err, "create quota event") + } + + deleted, err := s.repo.DeleteExpiredQuotaEventsBatch(s.ctx, now, 2) + s.Require().NoError(err, "DeleteExpiredQuotaEventsBatch first run") + s.Require().Equal(int64(2), deleted) + + deleted, err = s.repo.DeleteExpiredQuotaEventsBatch(s.ctx, now, 2) + s.Require().NoError(err, "DeleteExpiredQuotaEventsBatch second run") + s.Require().Equal(int64(1), deleted) + + deleted, err = s.repo.DeleteExpiredQuotaEventsBatch(s.ctx, now, 2) + s.Require().NoError(err, "DeleteExpiredQuotaEventsBatch third run") + s.Require().Equal(int64(0), deleted) + + var remaining int + err = scanSingleRow(s.ctx, s.client, ` + SELECT COUNT(*) + FROM user_subscription_quota_events + WHERE user_subscription_id = $1 + `, []any{sub.ID}, &remaining) + s.Require().NoError(err, "count remaining quota events") + s.Require().Equal(1, remaining, "only the future event should remain") +} + // --- ExistsByUserIDAndGroupID --- func (s *UserSubscriptionRepoSuite) TestExistsByUserIDAndGroupID() { diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go index 99c7c423282..39285799f3a 100644 --- a/backend/internal/server/api_contract_test.go +++ b/backend/internal/server/api_contract_test.go @@ -360,6 +360,7 @@ func TestAPIContracts(t *testing.T) { "daily_limit_usd": null, "weekly_limit_usd": null, "monthly_limit_usd": null, + "total_limit_usd": null, "image_price_1k": null, "image_price_2k": null, "image_price_4k": null, @@ -434,6 +435,11 @@ func TestAPIContracts(t *testing.T) { "daily_usage_usd": 1.23, "weekly_usage_usd": 2.34, "monthly_usage_usd": 3.45, + "total_limit_usd": 0, + "total_used_usd": 0, + "total_remaining_usd": 0, + "next_expiring_quota_usd": 0, + "next_quota_expire_at": null, "created_at": "2025-01-02T03:04:05Z", "updated_at": "2025-01-02T03:04:05Z" } @@ -2183,9 +2189,24 @@ func (stubUserSubscriptionRepo) ResetMonthlyUsage(ctx context.Context, id int64, func (stubUserSubscriptionRepo) IncrementUsage(ctx context.Context, id int64, costUSD float64) error { return errors.New("not implemented") } +func (stubUserSubscriptionRepo) CreateQuotaEvent(ctx context.Context, event *service.UserSubscriptionQuotaEvent) error { + return errors.New("not implemented") +} +func (stubUserSubscriptionRepo) RetireDepletedQuotaEventsOnAppend(ctx context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error { + return errors.New("not implemented") +} +func (stubUserSubscriptionRepo) GetQuotaSummary(ctx context.Context, subscriptionID int64, now time.Time) (*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} +func (stubUserSubscriptionRepo) GetQuotaSummaryBatch(ctx context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} func (stubUserSubscriptionRepo) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) { return 0, errors.New("not implemented") } +func (stubUserSubscriptionRepo) DeleteExpiredQuotaEventsBatch(ctx context.Context, now time.Time, limit int) (int64, error) { + return 0, errors.New("not implemented") +} type stubApiKeyRepo struct { now time.Time diff --git a/backend/internal/server/middleware/api_key_auth.go b/backend/internal/server/middleware/api_key_auth.go index a595fbdb3da..eedb4e41c22 100644 --- a/backend/internal/server/middleware/api_key_auth.go +++ b/backend/internal/server/middleware/api_key_auth.go @@ -251,13 +251,28 @@ func apiKeyAuthWithSubscription(apiKeyService *service.APIKeyService, subscripti status := 403 if errors.Is(validateErr, service.ErrDailyLimitExceeded) || errors.Is(validateErr, service.ErrWeeklyLimitExceeded) || - errors.Is(validateErr, service.ErrMonthlyLimitExceeded) { + errors.Is(validateErr, service.ErrMonthlyLimitExceeded) || + errors.Is(validateErr, service.ErrTotalLimitExceeded) { code = "USAGE_LIMIT_EXCEEDED" status = 429 } AbortWithError(c, status, code, validateErr.Error()) return } + if apiKey.Group != nil && apiKey.Group.IsTotalQuotaSubscriptionType() { + if snapshot, err := subscriptionService.BuildTotalQuotaSpendSnapshot(c.Request.Context(), subscription); err != nil { + code := "SUBSCRIPTION_INVALID" + status := 403 + if errors.Is(err, service.ErrTotalLimitExceeded) { + code = "USAGE_LIMIT_EXCEEDED" + status = 429 + } + AbortWithError(c, status, code, err.Error()) + return + } else if snapshot != nil { + c.Request = c.Request.WithContext(service.WithTotalQuotaSpendSnapshot(c.Request.Context(), snapshot)) + } + } } else { // 非订阅模式 或 订阅模式但 subscriptionService 未注入:回退到余额检查 if apiKeyBalanceBelowAuthThreshold(apiKey.User.Balance, cfg) { diff --git a/backend/internal/server/middleware/api_key_auth_google.go b/backend/internal/server/middleware/api_key_auth_google.go index 9efbbcb2cc9..e892256dd70 100644 --- a/backend/internal/server/middleware/api_key_auth_google.go +++ b/backend/internal/server/middleware/api_key_auth_google.go @@ -191,12 +191,25 @@ func APIKeyAuthWithSubscriptionGoogle(apiKeyService *service.APIKeyService, subs status := 403 if errors.Is(err, service.ErrDailyLimitExceeded) || errors.Is(err, service.ErrWeeklyLimitExceeded) || - errors.Is(err, service.ErrMonthlyLimitExceeded) { + errors.Is(err, service.ErrMonthlyLimitExceeded) || + errors.Is(err, service.ErrTotalLimitExceeded) { status = 429 } abortWithGoogleError(c, status, err.Error()) return } + if apiKey.Group != nil && apiKey.Group.IsTotalQuotaSubscriptionType() { + if snapshot, snapErr := subscriptionService.BuildTotalQuotaSpendSnapshot(c.Request.Context(), subscription); snapErr != nil { + status := 403 + if errors.Is(snapErr, service.ErrTotalLimitExceeded) { + status = 429 + } + abortWithGoogleError(c, status, snapErr.Error()) + return + } else if snapshot != nil { + c.Request = c.Request.WithContext(service.WithTotalQuotaSpendSnapshot(c.Request.Context(), snapshot)) + } + } c.Set(string(ContextKeySubscription), subscription) } else { diff --git a/backend/internal/server/middleware/api_key_auth_google_test.go b/backend/internal/server/middleware/api_key_auth_google_test.go index 1b1e196a2d6..ad92ab46f1b 100644 --- a/backend/internal/server/middleware/api_key_auth_google_test.go +++ b/backend/internal/server/middleware/api_key_auth_google_test.go @@ -257,9 +257,24 @@ func (f fakeGoogleSubscriptionRepo) ResetMonthlyUsage(ctx context.Context, id in func (f fakeGoogleSubscriptionRepo) IncrementUsage(ctx context.Context, id int64, costUSD float64) error { return errors.New("not implemented") } +func (f fakeGoogleSubscriptionRepo) CreateQuotaEvent(ctx context.Context, event *service.UserSubscriptionQuotaEvent) error { + return errors.New("not implemented") +} +func (f fakeGoogleSubscriptionRepo) RetireDepletedQuotaEventsOnAppend(ctx context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error { + return errors.New("not implemented") +} +func (f fakeGoogleSubscriptionRepo) GetQuotaSummary(ctx context.Context, subscriptionID int64, now time.Time) (*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} +func (f fakeGoogleSubscriptionRepo) GetQuotaSummaryBatch(ctx context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} func (f fakeGoogleSubscriptionRepo) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) { return 0, errors.New("not implemented") } +func (f fakeGoogleSubscriptionRepo) DeleteExpiredQuotaEventsBatch(ctx context.Context, now time.Time, limit int) (int64, error) { + return 0, errors.New("not implemented") +} type googleErrorResponse struct { Error struct { diff --git a/backend/internal/server/middleware/api_key_auth_test.go b/backend/internal/server/middleware/api_key_auth_test.go index b6bad315ec7..a7995db8b1a 100644 --- a/backend/internal/server/middleware/api_key_auth_test.go +++ b/backend/internal/server/middleware/api_key_auth_test.go @@ -268,6 +268,75 @@ func TestSimpleModeBypassesQuotaCheck(t *testing.T) { require.Equal(t, http.StatusTooManyRequests, w.Code) require.Contains(t, w.Body.String(), "USAGE_LIMIT_EXCEEDED") }) + + t.Run("standard_mode_enforces_total_quota_limit", func(t *testing.T) { + totalLimit := 100.0 + totalQuotaGroup := &service.Group{ + ID: 52, + Name: "total-sub", + Status: service.StatusActive, + Hydrated: true, + SubscriptionType: service.SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + } + totalQuotaAPIKey := &service.APIKey{ + ID: 101, + UserID: user.ID, + Key: "total-key", + Status: service.StatusActive, + User: user, + Group: totalQuotaGroup, + } + totalQuotaAPIKey.GroupID = &totalQuotaGroup.ID + + totalQuotaRepo := &stubApiKeyRepo{ + getByKey: func(ctx context.Context, key string) (*service.APIKey, error) { + if key != totalQuotaAPIKey.Key { + return nil, service.ErrAPIKeyNotFound + } + clone := *totalQuotaAPIKey + return &clone, nil + }, + } + + cfg := &config.Config{RunMode: config.RunModeStandard} + apiKeyService := service.NewAPIKeyService(totalQuotaRepo, nil, nil, nil, nil, nil, cfg) + + sub := &service.UserSubscription{ + ID: 56, + UserID: user.ID, + GroupID: totalQuotaGroup.ID, + Status: service.SubscriptionStatusActive, + ExpiresAt: time.Now().Add(24 * time.Hour), + TotalLimitUSD: 100, + TotalUsedUSD: 100, + TotalRemainingUSD: 0, + } + subscriptionRepo := &stubUserSubscriptionRepo{ + getActive: func(ctx context.Context, userID, groupID int64) (*service.UserSubscription, error) { + if userID != sub.UserID || groupID != sub.GroupID { + return nil, service.ErrSubscriptionNotFound + } + clone := *sub + return &clone, nil + }, + updateStatus: func(ctx context.Context, subscriptionID int64, status string) error { return nil }, + activateWindow: func(ctx context.Context, id int64, start time.Time) error { return nil }, + resetDaily: func(ctx context.Context, id int64, start time.Time) error { return nil }, + resetWeekly: func(ctx context.Context, id int64, start time.Time) error { return nil }, + resetMonthly: func(ctx context.Context, id int64, start time.Time) error { return nil }, + } + subscriptionService := service.NewSubscriptionService(nil, subscriptionRepo, nil, nil, cfg) + router := newAuthTestRouter(apiKeyService, subscriptionService, cfg) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/t", nil) + req.Header.Set("x-api-key", totalQuotaAPIKey.Key) + router.ServeHTTP(w, req) + + require.Equal(t, http.StatusTooManyRequests, w.Code) + require.Contains(t, w.Body.String(), "USAGE_LIMIT_EXCEEDED") + }) } func TestAPIKeyAuthSetsGroupContext(t *testing.T) { @@ -1785,6 +1854,26 @@ func (r *stubUserSubscriptionRepo) IncrementUsage(ctx context.Context, id int64, return errors.New("not implemented") } +func (r *stubUserSubscriptionRepo) CreateQuotaEvent(ctx context.Context, event *service.UserSubscriptionQuotaEvent) error { + return errors.New("not implemented") +} + +func (r *stubUserSubscriptionRepo) RetireDepletedQuotaEventsOnAppend(ctx context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error { + return errors.New("not implemented") +} + +func (r *stubUserSubscriptionRepo) GetQuotaSummary(ctx context.Context, subscriptionID int64, now time.Time) (*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} + +func (r *stubUserSubscriptionRepo) GetQuotaSummaryBatch(ctx context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*service.UserSubscriptionQuotaSummary, error) { + return nil, errors.New("not implemented") +} + func (r *stubUserSubscriptionRepo) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) { return 0, errors.New("not implemented") } + +func (r *stubUserSubscriptionRepo) DeleteExpiredQuotaEventsBatch(ctx context.Context, now time.Time, limit int) (int64, error) { + return 0, errors.New("not implemented") +} diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index b6ad7b52790..85312716dac 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -209,10 +209,11 @@ type CreateGroupInput struct { Platform string RateMultiplier float64 IsExclusive bool - SubscriptionType string // standard/subscription + SubscriptionType string // standard/subscription/total_quota DailyLimitUSD *float64 // 日限额 (USD) WeeklyLimitUSD *float64 // 周限额 (USD) MonthlyLimitUSD *float64 // 月限额 (USD) + TotalLimitUSD *float64 // 总额度限额 (USD) // 图片生成计费配置(仅 antigravity 平台使用) AllowImageGeneration bool AllowBatchImageGeneration bool @@ -269,10 +270,11 @@ type UpdateGroupInput struct { RateMultiplier *float64 // 使用指针以支持设置为0 IsExclusive *bool Status string - SubscriptionType string // standard/subscription + SubscriptionType string // standard/subscription/total_quota DailyLimitUSD *float64 // 日限额 (USD) WeeklyLimitUSD *float64 // 周限额 (USD) MonthlyLimitUSD *float64 // 月限额 (USD) + TotalLimitUSD *float64 // 总额度限额 (USD) // 图片生成计费配置(仅 antigravity 平台使用) AllowImageGeneration *bool AllowBatchImageGeneration *bool diff --git a/backend/internal/service/billing_cache_port.go b/backend/internal/service/billing_cache_port.go index 00bb43daa84..7ad9e051e7e 100644 --- a/backend/internal/service/billing_cache_port.go +++ b/backend/internal/service/billing_cache_port.go @@ -6,10 +6,15 @@ import ( // SubscriptionCacheData represents cached subscription data type SubscriptionCacheData struct { - Status string - ExpiresAt time.Time - DailyUsage float64 - WeeklyUsage float64 - MonthlyUsage float64 - Version int64 + Status string + ExpiresAt time.Time + DailyUsage float64 + WeeklyUsage float64 + MonthlyUsage float64 + TotalLimit float64 + TotalUsed float64 + TotalRemaining float64 + NextQuotaExpireAt *time.Time + NextExpiringQuotaUSD float64 + Version int64 } diff --git a/backend/internal/service/billing_cache_service.go b/backend/internal/service/billing_cache_service.go index b1c6e0933ee..de84f87ad2e 100644 --- a/backend/internal/service/billing_cache_service.go +++ b/backend/internal/service/billing_cache_service.go @@ -40,12 +40,17 @@ var ( // subscriptionCacheData 订阅缓存数据结构(内部使用) type subscriptionCacheData struct { - Status string - ExpiresAt time.Time - DailyUsage float64 - WeeklyUsage float64 - MonthlyUsage float64 - Version int64 + Status string + ExpiresAt time.Time + DailyUsage float64 + WeeklyUsage float64 + MonthlyUsage float64 + TotalLimit float64 + TotalUsed float64 + TotalRemaining float64 + NextQuotaExpireAt *time.Time + NextExpiringQuotaUSD float64 + Version int64 } // 缓存写入任务类型 @@ -420,7 +425,12 @@ func (s *BillingCacheService) GetSubscriptionStatus(ctx context.Context, userID, // 尝试从缓存读取 cacheData, err := s.cache.GetSubscriptionCache(ctx, userID, groupID) if err == nil && cacheData != nil { - return s.convertFromPortsData(cacheData), nil + data := s.convertFromPortsData(cacheData) + if data.NextQuotaExpireAt != nil && !time.Now().Before(*data.NextQuotaExpireAt) { + _ = s.cache.InvalidateSubscriptionCache(ctx, userID, groupID) + } else { + return data, nil + } } // 缓存未命中,从数据库读取 @@ -442,23 +452,33 @@ func (s *BillingCacheService) GetSubscriptionStatus(ctx context.Context, userID, func (s *BillingCacheService) convertFromPortsData(data *SubscriptionCacheData) *subscriptionCacheData { return &subscriptionCacheData{ - Status: data.Status, - ExpiresAt: data.ExpiresAt, - DailyUsage: data.DailyUsage, - WeeklyUsage: data.WeeklyUsage, - MonthlyUsage: data.MonthlyUsage, - Version: data.Version, + Status: data.Status, + ExpiresAt: data.ExpiresAt, + DailyUsage: data.DailyUsage, + WeeklyUsage: data.WeeklyUsage, + MonthlyUsage: data.MonthlyUsage, + TotalLimit: data.TotalLimit, + TotalUsed: data.TotalUsed, + TotalRemaining: data.TotalRemaining, + NextQuotaExpireAt: data.NextQuotaExpireAt, + NextExpiringQuotaUSD: data.NextExpiringQuotaUSD, + Version: data.Version, } } func (s *BillingCacheService) convertToPortsData(data *subscriptionCacheData) *SubscriptionCacheData { return &SubscriptionCacheData{ - Status: data.Status, - ExpiresAt: data.ExpiresAt, - DailyUsage: data.DailyUsage, - WeeklyUsage: data.WeeklyUsage, - MonthlyUsage: data.MonthlyUsage, - Version: data.Version, + Status: data.Status, + ExpiresAt: data.ExpiresAt, + DailyUsage: data.DailyUsage, + WeeklyUsage: data.WeeklyUsage, + MonthlyUsage: data.MonthlyUsage, + TotalLimit: data.TotalLimit, + TotalUsed: data.TotalUsed, + TotalRemaining: data.TotalRemaining, + NextQuotaExpireAt: data.NextQuotaExpireAt, + NextExpiringQuotaUSD: data.NextExpiringQuotaUSD, + Version: data.Version, } } @@ -470,12 +490,17 @@ func (s *BillingCacheService) getSubscriptionFromDB(ctx context.Context, userID, } return &subscriptionCacheData{ - Status: sub.Status, - ExpiresAt: sub.ExpiresAt, - DailyUsage: sub.DailyUsageUSD, - WeeklyUsage: sub.WeeklyUsageUSD, - MonthlyUsage: sub.MonthlyUsageUSD, - Version: sub.UpdatedAt.Unix(), + Status: sub.Status, + ExpiresAt: sub.ExpiresAt, + DailyUsage: sub.DailyUsageUSD, + WeeklyUsage: sub.WeeklyUsageUSD, + MonthlyUsage: sub.MonthlyUsageUSD, + TotalLimit: sub.TotalLimitUSD, + TotalUsed: sub.TotalUsedUSD, + TotalRemaining: sub.TotalRemainingUSD, + NextQuotaExpireAt: sub.NextQuotaExpireAt, + NextExpiringQuotaUSD: sub.NextExpiringQuotaUSD, + Version: sub.UpdatedAt.Unix(), }, nil } @@ -922,6 +947,13 @@ func (s *BillingCacheService) checkSubscriptionEligibility(ctx context.Context, } // 检查限额(使用传入的Group限额配置) + if group.IsTotalQuotaSubscriptionType() { + if group.HasTotalLimit() && subData.TotalRemaining <= 0 { + return ErrTotalLimitExceeded + } + return nil + } + if group.HasDailyLimit() && subData.DailyUsage >= *group.DailyLimitUSD { return ErrDailyLimitExceeded } diff --git a/backend/internal/service/content_moderation_runtime_cache_test.go b/backend/internal/service/content_moderation_runtime_cache_test.go index 2b34d1fa578..7c11e8b5b20 100644 --- a/backend/internal/service/content_moderation_runtime_cache_test.go +++ b/backend/internal/service/content_moderation_runtime_cache_test.go @@ -265,13 +265,18 @@ func TestContentModerationRuntimeSnapshotRefreshFailureKeepsStaleConfig(t *testi SettingKeyRiskControlEnabled: "true", SettingKeyContentModerationConfig: runtimeCacheTestConfig(t, "blocked"), }} - svc := runtimeCacheTestService(repo, time.Nanosecond) + svc := runtimeCacheTestService(repo, time.Minute) input := runtimeCacheTestInput("blocked") decision, err := svc.Check(context.Background(), input) require.NoError(t, err) require.True(t, decision.Blocked) + current := svc.runtimeSnapshot.Load() + require.NotNil(t, current) + expired := *current + expired.loadedAt = time.Now().Add(-2 * time.Minute) + svc.runtimeSnapshot.Store(&expired) repo.failMultiple(errors.New("database unavailable")) decision, err = svc.Check(context.Background(), input) require.NoError(t, err) diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index e503cfacab7..7d8f36dd42c 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -101,6 +101,7 @@ const ( const ( SubscriptionTypeStandard = domain.SubscriptionTypeStandard // 标准计费模式(按余额扣费) SubscriptionTypeSubscription = domain.SubscriptionTypeSubscription // 订阅模式(按限额控制) + SubscriptionTypeTotalQuota = domain.SubscriptionTypeTotalQuota // 总额度订阅模式(按事件总额度控制) ) // Subscription status constants diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go index e428035db5f..d3c674cb379 100644 --- a/backend/internal/service/group.go +++ b/backend/internal/service/group.go @@ -37,6 +37,7 @@ type Group struct { DailyLimitUSD *float64 WeeklyLimitUSD *float64 MonthlyLimitUSD *float64 + TotalLimitUSD *float64 DefaultValidityDays int // 图片生成计费配置(antigravity 和 gemini 平台使用) @@ -112,9 +113,17 @@ func (g *Group) IsActive() bool { } func (g *Group) IsSubscriptionType() bool { + return g.IsWindowedSubscriptionType() || g.IsTotalQuotaSubscriptionType() +} + +func (g *Group) IsWindowedSubscriptionType() bool { return g.SubscriptionType == SubscriptionTypeSubscription } +func (g *Group) IsTotalQuotaSubscriptionType() bool { + return g.SubscriptionType == SubscriptionTypeTotalQuota +} + func (g *Group) HasDailyLimit() bool { return g.DailyLimitUSD != nil && *g.DailyLimitUSD > 0 } @@ -127,6 +136,10 @@ func (g *Group) HasMonthlyLimit() bool { return g.MonthlyLimitUSD != nil && *g.MonthlyLimitUSD > 0 } +func (g *Group) HasTotalLimit() bool { + return g.TotalLimitUSD != nil && *g.TotalLimitUSD > 0 +} + // GetImagePrice 根据 image_size 返回对应的图片生成价格 // 如果分组未配置价格,返回 nil(调用方应使用默认值) func (g *Group) GetImagePrice(imageSize string) *float64 { diff --git a/backend/internal/service/openai_images.go b/backend/internal/service/openai_images.go index f32fc56159b..039fd4f2b6d 100644 --- a/backend/internal/service/openai_images.go +++ b/backend/internal/service/openai_images.go @@ -37,8 +37,8 @@ const ( openAIChatGPTStartURL = "https://chatgpt.com/" openAIChatGPTFilesURL = "https://chatgpt.com/backend-api/files" openAIImageBackendUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" - openAIImageMaxDownloadBytes = 20 << 20 // 20MB per image download - openAIImageMaxUploadPartSize = 20 << 20 // 20MB per multipart upload part + openAIImageMaxDownloadBytes = 50 << 20 // 50MB per image download + openAIImageMaxUploadPartSize = 50 << 20 // 50MB per multipart upload part openAIImagesResponsesMainModel = "gpt-5.4-mini" ) diff --git a/backend/internal/service/openai_sse_concatenated_json_test.go b/backend/internal/service/openai_sse_concatenated_json_test.go index 27c057157ee..9bfb835f7f1 100644 --- a/backend/internal/service/openai_sse_concatenated_json_test.go +++ b/backend/internal/service/openai_sse_concatenated_json_test.go @@ -398,6 +398,9 @@ func openAIConcatenatedJSONTestEvents(t *testing.T) (string, string, string) { err := json.Unmarshal([]byte(largeInProgress+outputItemAdded), &decoded) var syntaxErr *json.SyntaxError require.ErrorAs(t, err, &syntaxErr) - require.Equal(t, int64(javascriptErrorPosition+1), syntaxErr.Offset) + require.Contains(t, []int64{ + int64(javascriptErrorPosition), + int64(javascriptErrorPosition + 1), + }, syntaxErr.Offset) return largeInProgress, outputItemAdded, completed } diff --git a/backend/internal/service/payment_config_plans.go b/backend/internal/service/payment_config_plans.go index 095730c1e8a..5561bc94c50 100644 --- a/backend/internal/service/payment_config_plans.go +++ b/backend/internal/service/payment_config_plans.go @@ -129,13 +129,65 @@ func (s *PaymentConfigService) ListPlans(ctx context.Context) ([]*dbent.Subscrip } func (s *PaymentConfigService) ListPlansForSale(ctx context.Context) ([]*dbent.SubscriptionPlan, error) { - return s.entClient.SubscriptionPlan.Query().Where(subscriptionplan.ForSaleEQ(true)).Order(subscriptionplan.BySortOrder()).All(ctx) + plans, err := s.entClient.SubscriptionPlan.Query(). + Where(subscriptionplan.ForSaleEQ(true)). + Order(subscriptionplan.BySortOrder()). + All(ctx) + if err != nil { + return nil, err + } + if len(plans) == 0 { + return plans, nil + } + + groupIDs := make([]int64, 0, len(plans)) + seen := make(map[int64]struct{}, len(plans)) + for _, plan := range plans { + if _, ok := seen[plan.GroupID]; ok { + continue + } + seen[plan.GroupID] = struct{}{} + groupIDs = append(groupIDs, plan.GroupID) + } + + activeGroups, err := s.entClient.Group.Query(). + Where(group.IDIn(groupIDs...), group.SubscriptionTypeEQ(SubscriptionTypeSubscription)). + All(ctx) + if err != nil { + return nil, err + } + allowed := make(map[int64]struct{}, len(activeGroups)) + for _, g := range activeGroups { + allowed[g.ID] = struct{}{} + } + + filtered := make([]*dbent.SubscriptionPlan, 0, len(plans)) + for _, plan := range plans { + if _, ok := allowed[plan.GroupID]; ok { + filtered = append(filtered, plan) + } + } + return filtered, nil +} + +func (s *PaymentConfigService) validatePlanGroup(ctx context.Context, groupID int64) error { + g, err := s.entClient.Group.Get(ctx, groupID) + if err != nil { + return infraerrors.NotFound("PLAN_GROUP_NOT_FOUND", "subscription plan group not found") + } + if g.SubscriptionType != SubscriptionTypeSubscription { + return infraerrors.BadRequest("PLAN_GROUP_INVALID", "only windowed subscription groups can be sold as plans") + } + return nil } func (s *PaymentConfigService) CreatePlan(ctx context.Context, req CreatePlanRequest) (*dbent.SubscriptionPlan, error) { if err := validatePlanRequired(req.Name, req.GroupID, req.Price, req.ValidityDays, req.ValidityUnit, req.OriginalPrice); err != nil { return nil, err } + if err := s.validatePlanGroup(ctx, req.GroupID); err != nil { + return nil, err + } currency, err := normalizePlanCurrency(req.Currency) if err != nil { return nil, err @@ -158,6 +210,11 @@ func (s *PaymentConfigService) UpdatePlan(ctx context.Context, id int64, req Upd if err := validatePlanPatch(req); err != nil { return nil, err } + if req.GroupID != nil { + if err := s.validatePlanGroup(ctx, *req.GroupID); err != nil { + return nil, err + } + } u := s.entClient.SubscriptionPlan.UpdateOneID(id) if req.GroupID != nil { u.SetGroupID(*req.GroupID) diff --git a/backend/internal/service/payment_order.go b/backend/internal/service/payment_order.go index 9b7ee08990a..f41db09220d 100644 --- a/backend/internal/service/payment_order.go +++ b/backend/internal/service/payment_order.go @@ -143,7 +143,7 @@ func (s *PaymentService) validateSubOrder(ctx context.Context, req CreateOrderRe if err != nil || group.Status != payment.EntityStatusActive { return nil, infraerrors.NotFound("GROUP_NOT_FOUND", "subscription group is no longer available") } - if !group.IsSubscriptionType() { + if !group.IsWindowedSubscriptionType() { return nil, infraerrors.BadRequest("GROUP_TYPE_MISMATCH", "group is not a subscription type") } return plan, nil diff --git a/backend/internal/service/redeem_service.go b/backend/internal/service/redeem_service.go index 8794872e3d1..e1091703d6f 100644 --- a/backend/internal/service/redeem_service.go +++ b/backend/internal/service/redeem_service.go @@ -493,11 +493,13 @@ func (s *RedeemService) Redeem(ctx context.Context, userID int64, code string) ( validityDays = 30 } _, _, err := s.subscriptionService.AssignOrExtendSubscription(txCtx, &AssignSubscriptionInput{ - UserID: userID, - GroupID: *redeemCode.GroupID, - ValidityDays: validityDays, - AssignedBy: 0, // 系统分配 - Notes: fmt.Sprintf("通过兑换码 %s 兑换", redeemCode.Code), + UserID: userID, + GroupID: *redeemCode.GroupID, + ValidityDays: validityDays, + AssignedBy: 0, // 系统分配 + Notes: fmt.Sprintf("通过兑换码 %s 兑换", redeemCode.Code), + EventSourceKind: SubscriptionQuotaSourceRedeemCode, + EventSourceRef: redeemCode.Code, }) if err != nil { return nil, fmt.Errorf("assign or extend subscription: %w", err) diff --git a/backend/internal/service/request_metadata.go b/backend/internal/service/request_metadata.go index 5c81bbf1220..b0722a1578f 100644 --- a/backend/internal/service/request_metadata.go +++ b/backend/internal/service/request_metadata.go @@ -18,6 +18,7 @@ type RequestMetadata struct { PrefetchedStickyGroupID *int64 SingleAccountRetry *bool AccountSwitchCount *int + TotalQuotaSpendSnapshot *TotalQuotaSpendSnapshot } var ( @@ -116,6 +117,12 @@ func WithAccountSwitchCount(ctx context.Context, value int, bridgeOldKeys bool) }) } +func WithTotalQuotaSpendSnapshot(ctx context.Context, snapshot *TotalQuotaSpendSnapshot) context.Context { + return updateRequestMetadata(ctx, false, func(md *RequestMetadata) { + md.TotalQuotaSpendSnapshot = snapshot + }, nil) +} + func IsMaxTokensOneHaikuRequestFromContext(ctx context.Context) (bool, bool) { if md := metadataFromContext(ctx); md != nil && md.IsMaxTokensOneHaikuRequest != nil { return *md.IsMaxTokensOneHaikuRequest, true @@ -214,3 +221,10 @@ func AccountSwitchCountFromContext(ctx context.Context) (int, bool) { } return 0, false } + +func TotalQuotaSpendSnapshotFromContext(ctx context.Context) (*TotalQuotaSpendSnapshot, bool) { + if md := metadataFromContext(ctx); md != nil && md.TotalQuotaSpendSnapshot != nil { + return md.TotalQuotaSpendSnapshot, true + } + return nil, false +} diff --git a/backend/internal/service/subscription_assign_idempotency_test.go b/backend/internal/service/subscription_assign_idempotency_test.go index 151b5c79280..1983ffd528e 100644 --- a/backend/internal/service/subscription_assign_idempotency_test.go +++ b/backend/internal/service/subscription_assign_idempotency_test.go @@ -7,6 +7,7 @@ import ( "time" dbent "github.com/Wei-Shaw/sub2api/ent" + "github.com/Wei-Shaw/sub2api/internal/config" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/Wei-Shaw/sub2api/internal/pkg/pagination" "github.com/dgraph-io/ristretto" @@ -172,9 +173,24 @@ func (userSubRepoNoop) ResetMonthlyUsage(context.Context, int64, *time.Time, tim func (userSubRepoNoop) IncrementUsage(context.Context, int64, float64) error { panic("unexpected IncrementUsage call") } +func (userSubRepoNoop) CreateQuotaEvent(context.Context, *UserSubscriptionQuotaEvent) error { + panic("unexpected CreateQuotaEvent call") +} +func (userSubRepoNoop) RetireDepletedQuotaEventsOnAppend(context.Context, int64, int64, time.Time) error { + panic("unexpected RetireDepletedQuotaEventsOnAppend call") +} +func (userSubRepoNoop) GetQuotaSummary(context.Context, int64, time.Time) (*UserSubscriptionQuotaSummary, error) { + panic("unexpected GetQuotaSummary call") +} +func (userSubRepoNoop) GetQuotaSummaryBatch(context.Context, []int64, time.Time) (map[int64]*UserSubscriptionQuotaSummary, error) { + panic("unexpected GetQuotaSummaryBatch call") +} func (userSubRepoNoop) BatchUpdateExpiredStatus(context.Context) (int64, error) { panic("unexpected BatchUpdateExpiredStatus call") } +func (userSubRepoNoop) DeleteExpiredQuotaEventsBatch(context.Context, time.Time, int) (int64, error) { + panic("unexpected DeleteExpiredQuotaEventsBatch call") +} type subscriptionUserSubRepoStub struct { userSubRepoNoop @@ -182,7 +198,9 @@ type subscriptionUserSubRepoStub struct { nextID int64 byID map[int64]*UserSubscription byUserGroup map[string]*UserSubscription + quotaEvents map[int64][]UserSubscriptionQuotaEvent createCalls int + retireCalls int } func newSubscriptionUserSubRepoStub() *subscriptionUserSubRepoStub { @@ -190,6 +208,7 @@ func newSubscriptionUserSubRepoStub() *subscriptionUserSubRepoStub { nextID: 1, byID: make(map[int64]*UserSubscription), byUserGroup: make(map[string]*UserSubscription), + quotaEvents: make(map[int64][]UserSubscriptionQuotaEvent), } } @@ -246,6 +265,31 @@ func (s *subscriptionUserSubRepoStub) GetByID(_ context.Context, id int64) (*Use return nil, ErrSubscriptionNotFound } cp := *sub + if events := s.quotaEvents[id]; len(events) > 0 { + cp.QuotaEvents = append([]UserSubscriptionQuotaEvent(nil), events...) + var nextExpiry *time.Time + for _, event := range events { + if event.ExpiresAt.After(time.Now()) { + cp.TotalLimitUSD += event.QuotaTotalUSD + cp.TotalUsedUSD += event.QuotaUsedUSD + remaining := maxFloat(event.QuotaTotalUSD-event.QuotaUsedUSD, 0) + cp.TotalRemainingUSD += remaining + if remaining <= 0 { + continue + } + if nextExpiry == nil || event.ExpiresAt.Before(*nextExpiry) { + exp := event.ExpiresAt + nextExpiry = &exp + cp.NextExpiringQuotaUSD = remaining + continue + } + if event.ExpiresAt.Equal(*nextExpiry) { + cp.NextExpiringQuotaUSD += remaining + } + } + } + cp.NextQuotaExpireAt = nextExpiry + } return &cp, nil } @@ -267,6 +311,135 @@ func (s *subscriptionUserSubRepoStub) Update(_ context.Context, sub *UserSubscri return nil } +func (s *subscriptionUserSubRepoStub) ExtendExpiry(_ context.Context, subscriptionID int64, newExpiresAt time.Time) error { + sub := s.byID[subscriptionID] + if sub == nil { + return ErrSubscriptionNotFound + } + sub.ExpiresAt = newExpiresAt + return nil +} + +func (s *subscriptionUserSubRepoStub) UpdateStatus(_ context.Context, subscriptionID int64, status string) error { + sub := s.byID[subscriptionID] + if sub == nil { + return ErrSubscriptionNotFound + } + sub.Status = status + return nil +} + +func (s *subscriptionUserSubRepoStub) UpdateNotes(_ context.Context, subscriptionID int64, notes string) error { + sub := s.byID[subscriptionID] + if sub == nil { + return ErrSubscriptionNotFound + } + sub.Notes = notes + return nil +} + +func (s *subscriptionUserSubRepoStub) CreateQuotaEvent(_ context.Context, event *UserSubscriptionQuotaEvent) error { + if event == nil { + return nil + } + cp := *event + cp.ID = int64(len(s.quotaEvents[event.UserSubscriptionID]) + 1) + event.ID = cp.ID + s.quotaEvents[event.UserSubscriptionID] = append(s.quotaEvents[event.UserSubscriptionID], cp) + return nil +} + +func (s *subscriptionUserSubRepoStub) RetireDepletedQuotaEventsOnAppend(_ context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error { + s.retireCalls++ + + events := s.quotaEvents[subscriptionID] + if len(events) == 0 { + return nil + } + + retireAt = retireAt.UTC() + var maxActiveExpiry time.Time + for i := range events { + if events[i].ID != keepEventID && + events[i].ExpiresAt.After(retireAt) && + events[i].QuotaTotalUSD-events[i].QuotaUsedUSD <= 1e-9 { + events[i].ExpiresAt = retireAt + } + if events[i].ExpiresAt.After(retireAt) && + (maxActiveExpiry.IsZero() || events[i].ExpiresAt.After(maxActiveExpiry)) { + maxActiveExpiry = events[i].ExpiresAt + } + } + s.quotaEvents[subscriptionID] = events + + if sub := s.byID[subscriptionID]; sub != nil && !maxActiveExpiry.IsZero() { + sub.ExpiresAt = maxActiveExpiry + } + return nil +} + +func (s *subscriptionUserSubRepoStub) GetQuotaSummary(_ context.Context, subscriptionID int64, now time.Time) (*UserSubscriptionQuotaSummary, error) { + summaries, err := s.GetQuotaSummaryBatch(context.Background(), []int64{subscriptionID}, now) + if err != nil { + return nil, err + } + if summary, ok := summaries[subscriptionID]; ok { + return summary, nil + } + return &UserSubscriptionQuotaSummary{}, nil +} + +func (s *subscriptionUserSubRepoStub) GetQuotaSummaryBatch(_ context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*UserSubscriptionQuotaSummary, error) { + result := make(map[int64]*UserSubscriptionQuotaSummary, len(subscriptionIDs)) + for _, subscriptionID := range subscriptionIDs { + summary := &UserSubscriptionQuotaSummary{} + var nextExpiry *time.Time + for _, event := range s.quotaEvents[subscriptionID] { + if !event.ExpiresAt.After(now) { + continue + } + summary.TotalLimitUSD += event.QuotaTotalUSD + summary.TotalUsedUSD += event.QuotaUsedUSD + remaining := maxFloat(event.QuotaTotalUSD-event.QuotaUsedUSD, 0) + summary.TotalRemainingUSD += remaining + if remaining <= 0 { + continue + } + if nextExpiry == nil || event.ExpiresAt.Before(*nextExpiry) { + exp := event.ExpiresAt + nextExpiry = &exp + summary.NextExpiringQuotaUSD = remaining + continue + } + if event.ExpiresAt.Equal(*nextExpiry) { + summary.NextExpiringQuotaUSD += remaining + } + } + summary.NextQuotaExpireAt = nextExpiry + result[subscriptionID] = summary + } + return result, nil +} + +func (s *subscriptionUserSubRepoStub) DeleteExpiredQuotaEventsBatch(_ context.Context, now time.Time, limit int) (int64, error) { + if limit <= 0 { + return 0, nil + } + + var deleted int64 + for subscriptionID, events := range s.quotaEvents { + filtered := events[:0] + for _, event := range events { + if deleted < int64(limit) && !event.ExpiresAt.After(now) { + deleted++ + continue + } + filtered = append(filtered, event) + } + s.quotaEvents[subscriptionID] = append([]UserSubscriptionQuotaEvent(nil), filtered...) + } + return deleted, nil +} func TestAssignSubscriptionReuseWhenSemanticsMatch(t *testing.T) { start := time.Now().Add(-time.Hour) groupRepo := &subscriptionGroupRepoStub{ @@ -528,6 +701,125 @@ func TestBulkAssignSubscriptionCreatedReusedAndConflict(t *testing.T) { require.Equal(t, 1, subRepo.createCalls) } +func TestAssignSubscriptionTotalQuota_AppendsEventOnExistingSubscription(t *testing.T) { + totalLimit := 100.0 + groupRepo := &subscriptionGroupRepoStub{ + group: &Group{ + ID: 1, + SubscriptionType: SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }, + } + subRepo := newSubscriptionUserSubRepoStub() + start := time.Now().Add(-time.Hour) + expiresAt := time.Now().Add(23 * time.Hour) + subRepo.seed(&UserSubscription{ + ID: 51, + UserID: 3001, + GroupID: 1, + StartsAt: start, + ExpiresAt: expiresAt, + Status: SubscriptionStatusActive, + }) + + svc := NewSubscriptionService(groupRepo, subRepo, nil, nil, &config.Config{}) + sub, err := svc.AssignSubscription(context.Background(), &AssignSubscriptionInput{ + UserID: 3001, + GroupID: 1, + ValidityDays: 2, + AssignedBy: 9, + EventSourceKind: SubscriptionQuotaSourceAdminAssign, + EventSourceRef: "admin:9", + }) + require.NoError(t, err) + require.NotNil(t, sub) + require.Equal(t, int64(51), sub.ID) + require.Len(t, subRepo.quotaEvents[51], 1) + require.Equal(t, "admin:9", subRepo.quotaEvents[51][0].SourceRef) + require.Equal(t, SubscriptionQuotaSourceAdminAssign, subRepo.quotaEvents[51][0].SourceKind) + require.Equal(t, 0, subRepo.createCalls, "should reuse existing subscription row") +} + +func TestAssignSubscriptionTotalQuota_RetiresDepletedEventsOnAppend(t *testing.T) { + totalLimit := 1.0 + groupRepo := &subscriptionGroupRepoStub{ + group: &Group{ + ID: 1, + SubscriptionType: SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }, + } + subRepo := newSubscriptionUserSubRepoStub() + now := time.Now().UTC() + subRepo.seed(&UserSubscription{ + ID: 52, + UserID: 3002, + GroupID: 1, + StartsAt: now.Add(-2 * time.Hour), + ExpiresAt: now.Add(48 * time.Hour), + Status: SubscriptionStatusActive, + }) + subRepo.quotaEvents[52] = []UserSubscriptionQuotaEvent{ + { + ID: 1, + UserSubscriptionID: 52, + QuotaTotalUSD: 1, + QuotaUsedUSD: 1.5, + StartsAt: now.Add(-2 * time.Hour), + ExpiresAt: now.Add(48 * time.Hour), + SourceKind: SubscriptionQuotaSourceRedeemCode, + SourceRef: "old", + }, + } + + svc := NewSubscriptionService(groupRepo, subRepo, nil, nil, &config.Config{}) + sub, err := svc.AssignSubscription(context.Background(), &AssignSubscriptionInput{ + UserID: 3002, + GroupID: 1, + ValidityDays: 1, + AssignedBy: 9, + EventSourceKind: SubscriptionQuotaSourceAdminAssign, + EventSourceRef: "admin:9", + }) + require.NoError(t, err) + require.NotNil(t, sub) + require.Equal(t, 1, subRepo.retireCalls) + require.Len(t, subRepo.quotaEvents[52], 2) + require.False(t, subRepo.quotaEvents[52][0].ExpiresAt.After(time.Now().UTC()), "depleted legacy event should be retired immediately") + require.True(t, subRepo.quotaEvents[52][1].ExpiresAt.After(time.Now().UTC()), "new event should stay active") + require.InDelta(t, 1.0, sub.TotalLimitUSD, 1e-9) + require.InDelta(t, 0.0, sub.TotalUsedUSD, 1e-9) + require.InDelta(t, 1.0, sub.TotalRemainingUSD, 1e-9) + require.WithinDuration(t, subRepo.quotaEvents[52][1].ExpiresAt, sub.ExpiresAt, time.Second) +} + +func TestAssignSubscriptionTotalQuota_FirstCreateDoesNotRetireEvents(t *testing.T) { + totalLimit := 2.0 + groupRepo := &subscriptionGroupRepoStub{ + group: &Group{ + ID: 1, + SubscriptionType: SubscriptionTypeTotalQuota, + TotalLimitUSD: &totalLimit, + }, + } + subRepo := newSubscriptionUserSubRepoStub() + + svc := NewSubscriptionService(groupRepo, subRepo, nil, nil, &config.Config{}) + sub, err := svc.AssignSubscription(context.Background(), &AssignSubscriptionInput{ + UserID: 3003, + GroupID: 1, + ValidityDays: 1, + AssignedBy: 9, + EventSourceKind: SubscriptionQuotaSourceAdminAssign, + EventSourceRef: "admin:9", + }) + require.NoError(t, err) + require.NotNil(t, sub) + require.Equal(t, 1, subRepo.createCalls) + require.Equal(t, 0, subRepo.retireCalls) + require.Len(t, subRepo.quotaEvents[sub.ID], 1) +} + func TestBulkAssignSubscriptionRenewsExpiredSemanticMatch(t *testing.T) { groupRepo := &subscriptionGroupRepoStub{ group: &Group{ID: 1, SubscriptionType: SubscriptionTypeSubscription}, @@ -664,3 +956,10 @@ func strconvFormatInt(v int64) string { func infraerrorsReason(err error) string { return infraerrors.Reason(err) } + +func maxFloat(a, b float64) float64 { + if a > b { + return a + } + return b +} diff --git a/backend/internal/service/subscription_expiry_service.go b/backend/internal/service/subscription_expiry_service.go index c93c763f029..0e38dbab68b 100644 --- a/backend/internal/service/subscription_expiry_service.go +++ b/backend/internal/service/subscription_expiry_service.go @@ -24,6 +24,8 @@ const ( subscriptionExpiryReminderLeaderLockTTL = 5 * time.Minute ) +const subscriptionQuotaEventCleanupBatchSize = 1000 + // SubscriptionExpiryService periodically updates expired subscription status. type SubscriptionExpiryService struct { userSubRepo UserSubscriptionRepository @@ -111,11 +113,44 @@ func (s *SubscriptionExpiryService) runOnce() { if updated > 0 { log.Printf("[SubscriptionExpiry] Updated %d expired subscriptions", updated) } + if !s.cleanupExpiredQuotaEvents(ctx) || ctx.Err() != nil { + return + } s.sendExpiryReminders(ctx) } +func (s *SubscriptionExpiryService) cleanupExpiredQuotaEvents(ctx context.Context) bool { + if s == nil || s.userSubRepo == nil { + return true + } + cleanupBefore := time.Now().UTC() + var deletedTotal int64 + for { + if err := ctx.Err(); err != nil { + if deletedTotal > 0 { + log.Printf("[SubscriptionExpiry] Deleted %d expired quota events before stop", deletedTotal) + } + return false + } + + deleted, err := s.userSubRepo.DeleteExpiredQuotaEventsBatch(ctx, cleanupBefore, subscriptionQuotaEventCleanupBatchSize) + if err != nil { + log.Printf("[SubscriptionExpiry] Delete expired quota events failed: %v", err) + return false + } + if deleted == 0 { + break + } + deletedTotal += deleted + } + if deletedTotal > 0 { + log.Printf("[SubscriptionExpiry] Deleted %d expired quota events", deletedTotal) + } + return true +} + func (s *SubscriptionExpiryService) sendExpiryReminders(ctx context.Context) { - if s == nil || s.userSubRepo == nil || s.notificationEmailService == nil { + if s == nil || s.userSubRepo == nil || s.notificationEmailService == nil || ctx.Err() != nil { return } if !s.expiryReminderEnabled(ctx) { @@ -130,6 +165,9 @@ func (s *SubscriptionExpiryService) sendExpiryReminders(ctx context.Context) { } defer release() for page := 1; ; page++ { + if ctx.Err() != nil { + return + } subs, pag, err := s.userSubRepo.List(ctx, pagination.PaginationParams{Page: page, PageSize: 200}, nil, nil, SubscriptionStatusActive, "", "expires_at", "asc") if err != nil { log.Printf("[SubscriptionExpiry] List active subscriptions for reminder failed: %v", err) diff --git a/backend/internal/service/subscription_expiry_service_test.go b/backend/internal/service/subscription_expiry_service_test.go index 7db642c076d..6c8929ff7ce 100644 --- a/backend/internal/service/subscription_expiry_service_test.go +++ b/backend/internal/service/subscription_expiry_service_test.go @@ -11,51 +11,18 @@ import ( ) type subscriptionExpiryRepoStub struct { - listCalls int -} - -func (r *subscriptionExpiryRepoStub) Create(context.Context, *UserSubscription) error { - return nil -} - -func (r *subscriptionExpiryRepoStub) GetByID(context.Context, int64) (*UserSubscription, error) { - return nil, ErrSubscriptionNotFound -} - -func (r *subscriptionExpiryRepoStub) GetByIDIncludeDeleted(context.Context, int64) (*UserSubscription, error) { - return nil, ErrSubscriptionNotFound -} - -func (r *subscriptionExpiryRepoStub) GetByUserIDAndGroupID(context.Context, int64, int64) (*UserSubscription, error) { - return nil, ErrSubscriptionNotFound -} + userSubRepoNoop -func (r *subscriptionExpiryRepoStub) GetActiveByUserIDAndGroupID(context.Context, int64, int64) (*UserSubscription, error) { - return nil, ErrSubscriptionNotFound -} + updateResult int64 + updateErr error + deleteErr error -func (r *subscriptionExpiryRepoStub) Update(context.Context, *UserSubscription) error { - return nil -} - -func (r *subscriptionExpiryRepoStub) Delete(context.Context, int64) error { - return nil -} - -func (r *subscriptionExpiryRepoStub) Restore(context.Context, int64, string) (*UserSubscription, error) { - return nil, ErrSubscriptionNotFound -} - -func (r *subscriptionExpiryRepoStub) ListByUserID(context.Context, int64) ([]UserSubscription, error) { - return nil, nil -} - -func (r *subscriptionExpiryRepoStub) ListActiveByUserID(context.Context, int64) ([]UserSubscription, error) { - return nil, nil -} - -func (r *subscriptionExpiryRepoStub) ListByGroupID(context.Context, int64, pagination.PaginationParams) ([]UserSubscription, *pagination.PaginationResult, error) { - return nil, nil, nil + batchUpdateCalls int + deleteCalls int + deleteResults []int64 + deleteCutoffs []time.Time + deleteLimits []int + listCalls int } func (r *subscriptionExpiryRepoStub) List(context.Context, pagination.PaginationParams, *int64, *int64, string, string, string, string) ([]UserSubscription, *pagination.PaginationResult, error) { @@ -108,7 +75,23 @@ func (r *subscriptionExpiryRepoStub) IncrementUsage(context.Context, int64, floa } func (r *subscriptionExpiryRepoStub) BatchUpdateExpiredStatus(context.Context) (int64, error) { - return 0, nil + r.batchUpdateCalls++ + return r.updateResult, r.updateErr +} + +func (r *subscriptionExpiryRepoStub) DeleteExpiredQuotaEventsBatch(_ context.Context, now time.Time, limit int) (int64, error) { + r.deleteCalls++ + r.deleteCutoffs = append(r.deleteCutoffs, now) + r.deleteLimits = append(r.deleteLimits, limit) + if r.deleteErr != nil { + return 0, r.deleteErr + } + if len(r.deleteResults) == 0 { + return 0, nil + } + result := r.deleteResults[0] + r.deleteResults = r.deleteResults[1:] + return result, nil } type subscriptionExpirySettingRepoStub struct { @@ -151,6 +134,32 @@ func (r *subscriptionExpirySettingRepoStub) Delete(context.Context, string) erro return nil } +func TestSubscriptionExpiryServiceRunOnce_CleansExpiredQuotaEventsInBatches(t *testing.T) { + repo := &subscriptionExpiryRepoStub{ + updateResult: 1, + deleteResults: []int64{ + 2, + 1, + 0, + }, + } + svc := NewSubscriptionExpiryService(repo, time.Minute) + + svc.runOnce() + + require.Equal(t, 1, repo.batchUpdateCalls) + require.Equal(t, 3, repo.deleteCalls) + require.Len(t, repo.deleteCutoffs, 3) + require.Equal(t, []int{ + subscriptionQuotaEventCleanupBatchSize, + subscriptionQuotaEventCleanupBatchSize, + subscriptionQuotaEventCleanupBatchSize, + }, repo.deleteLimits) + for i := 1; i < len(repo.deleteCutoffs); i++ { + require.True(t, repo.deleteCutoffs[i].Equal(repo.deleteCutoffs[0]), "cleanup cutoff should stay stable within one run") + } +} + func TestSubscriptionExpiryService_ExpiryReminderEnabledDefaultsToTrue(t *testing.T) { svc := NewSubscriptionExpiryService(nil, time.Minute) svc.SetSettingRepository(&subscriptionExpirySettingRepoStub{values: map[string]string{}}) diff --git a/backend/internal/service/subscription_service.go b/backend/internal/service/subscription_service.go index fbe99bf9063..7bb1843b044 100644 --- a/backend/internal/service/subscription_service.go +++ b/backend/internal/service/subscription_service.go @@ -24,6 +24,11 @@ var MaxExpiresAt = time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC) // MaxValidityDays is the maximum allowed validity days for subscriptions (100 years) const MaxValidityDays = 36500 +const ( + SubscriptionQuotaSourceAdminAssign = "admin_assign" + SubscriptionQuotaSourceRedeemCode = "redeem_code" +) + var ( ErrSubscriptionNotFound = infraerrors.NotFound("SUBSCRIPTION_NOT_FOUND", "subscription not found") ErrSubscriptionExpired = infraerrors.Forbidden("SUBSCRIPTION_EXPIRED", "subscription has expired") @@ -33,6 +38,10 @@ var ( ErrSubscriptionNotRevoked = infraerrors.Conflict("SUBSCRIPTION_NOT_REVOKED", "subscription is not revoked") ErrSubscriptionRestoreConflict = infraerrors.Conflict("SUBSCRIPTION_RESTORE_CONFLICT", "subscription already exists for this user and group") ErrGroupNotSubscriptionType = infraerrors.BadRequest("GROUP_NOT_SUBSCRIPTION_TYPE", "group is not a subscription type") + ErrTotalLimitExceeded = infraerrors.TooManyRequests("TOTAL_LIMIT_EXCEEDED", "total quota limit exceeded") + ErrTotalQuotaLimitRequired = infraerrors.BadRequest("TOTAL_QUOTA_LIMIT_REQUIRED", "total quota subscription requires total limit") + ErrTotalQuotaAdjustUnsupported = infraerrors.BadRequest("TOTAL_QUOTA_ADJUST_UNSUPPORTED", "total quota subscriptions do not support manual adjustment") + ErrTotalQuotaResetUnsupported = infraerrors.BadRequest("TOTAL_QUOTA_RESET_UNSUPPORTED", "total quota subscriptions do not support quota reset") ErrInvalidInput = infraerrors.BadRequest("INVALID_INPUT", "at least one of resetDaily, resetWeekly, or resetMonthly must be true") ErrDailyLimitExceeded = infraerrors.TooManyRequests("DAILY_LIMIT_EXCEEDED", "daily usage limit exceeded") ErrWeeklyLimitExceeded = infraerrors.TooManyRequests("WEEKLY_LIMIT_EXCEEDED", "weekly usage limit exceeded") @@ -57,6 +66,10 @@ type SubscriptionService struct { maintenanceQueue *SubscriptionMaintenanceQueue } +type totalQuotaSpendSnapshotProvider interface { + GetQuotaSpendSnapshot(ctx context.Context, subscriptionID int64, now time.Time) (*TotalQuotaSpendSnapshot, error) +} + // NewSubscriptionService 创建订阅服务 func NewSubscriptionService(groupRepo GroupRepository, userSubRepo UserSubscriptionRepository, billingCacheService *BillingCacheService, entClient *dbent.Client, cfg *config.Config) *SubscriptionService { svc := &SubscriptionService{ @@ -189,11 +202,13 @@ func (s *SubscriptionService) invalidateSubscriptionCaches(userID, groupID int64 // AssignSubscriptionInput 分配订阅输入 type AssignSubscriptionInput struct { - UserID int64 - GroupID int64 - ValidityDays int - AssignedBy int64 - Notes string + UserID int64 + GroupID int64 + ValidityDays int + AssignedBy int64 + Notes string + EventSourceKind string + EventSourceRef string } // AssignSubscription 分配订阅给用户(不允许重复分配) @@ -224,6 +239,9 @@ func (s *SubscriptionService) assignOrExtendSubscription(ctx context.Context, in if !group.IsSubscriptionType() { return nil, false, ErrGroupNotSubscriptionType } + if group.IsTotalQuotaSubscriptionType() { + return s.assignOrAppendTotalQuotaSubscription(ctx, group, input) + } // 查询是否已有订阅 existingSub, err := s.userSubRepo.GetByUserIDAndGroupID(ctx, input.UserID, input.GroupID) @@ -462,11 +480,13 @@ func (s *SubscriptionService) BulkAssignSubscription(ctx context.Context, input for _, userID := range input.UserIDs { sub, reused, err := s.assignSubscriptionWithReuse(ctx, &AssignSubscriptionInput{ - UserID: userID, - GroupID: input.GroupID, - ValidityDays: input.ValidityDays, - AssignedBy: input.AssignedBy, - Notes: input.Notes, + UserID: userID, + GroupID: input.GroupID, + ValidityDays: input.ValidityDays, + AssignedBy: input.AssignedBy, + Notes: input.Notes, + EventSourceKind: SubscriptionQuotaSourceAdminAssign, + EventSourceRef: buildAdminAssignSourceRef(input.AssignedBy), }) if err != nil { result.FailedCount++ @@ -497,6 +517,9 @@ func (s *SubscriptionService) assignSubscriptionWithReuse(ctx context.Context, i if !group.IsSubscriptionType() { return nil, false, ErrGroupNotSubscriptionType } + if group.IsTotalQuotaSubscriptionType() { + return s.assignOrAppendTotalQuotaSubscription(ctx, group, input) + } // 检查是否已存在订阅;若已存在,则按幂等成功返回现有订阅 exists, err := s.userSubRepo.ExistsByUserIDAndGroupID(ctx, input.UserID, input.GroupID) @@ -589,6 +612,165 @@ func normalizeAssignValidityDays(days int) int { return days } +func buildAdminAssignSourceRef(adminID int64) string { + if adminID <= 0 { + return "" + } + return "admin:" + strconv.FormatInt(adminID, 10) +} + +func (s *SubscriptionService) normalizeQuotaEventSource(input *AssignSubscriptionInput) (string, string) { + if input == nil { + return SubscriptionQuotaSourceAdminAssign, "" + } + kind := strings.TrimSpace(input.EventSourceKind) + ref := strings.TrimSpace(input.EventSourceRef) + switch kind { + case SubscriptionQuotaSourceAdminAssign, SubscriptionQuotaSourceRedeemCode: + return kind, ref + default: + if input.AssignedBy > 0 { + return SubscriptionQuotaSourceAdminAssign, buildAdminAssignSourceRef(input.AssignedBy) + } + return SubscriptionQuotaSourceAdminAssign, ref + } +} + +func (s *SubscriptionService) createQuotaEvent( + ctx context.Context, + subscriptionID int64, + group *Group, + input *AssignSubscriptionInput, + startsAt, expiresAt time.Time, +) (*UserSubscriptionQuotaEvent, error) { + if group == nil || !group.HasTotalLimit() { + return nil, ErrTotalQuotaLimitRequired + } + sourceKind, sourceRef := s.normalizeQuotaEventSource(input) + event := &UserSubscriptionQuotaEvent{ + UserSubscriptionID: subscriptionID, + QuotaTotalUSD: *group.TotalLimitUSD, + QuotaUsedUSD: 0, + StartsAt: startsAt, + ExpiresAt: expiresAt, + SourceKind: sourceKind, + SourceRef: sourceRef, + } + if err := s.userSubRepo.CreateQuotaEvent(ctx, event); err != nil { + return nil, err + } + return event, nil +} + +func (s *SubscriptionService) assignOrAppendTotalQuotaSubscription(ctx context.Context, group *Group, input *AssignSubscriptionInput) (*UserSubscription, bool, error) { + if group == nil || !group.IsTotalQuotaSubscriptionType() { + return nil, false, ErrGroupNotSubscriptionType + } + if !group.HasTotalLimit() { + return nil, false, ErrTotalQuotaLimitRequired + } + + validityDays := normalizeAssignValidityDays(input.ValidityDays) + now := time.Now() + eventExpiresAt := now.AddDate(0, 0, validityDays) + if eventExpiresAt.After(MaxExpiresAt) { + eventExpiresAt = MaxExpiresAt + } + + existingSub, err := s.userSubRepo.GetByUserIDAndGroupID(ctx, input.UserID, input.GroupID) + if err != nil { + existingSub = nil + } + + txCtx := ctx + var tx *dbent.Tx + if s.entClient != nil { + var err error + tx, err = s.entClient.Tx(ctx) + if err != nil { + return nil, false, fmt.Errorf("begin transaction: %w", err) + } + defer func() { _ = tx.Rollback() }() + txCtx = dbent.NewTxContext(ctx, tx) + } + subscriptionID := int64(0) + reused := false + + if existingSub != nil { + reused = true + subscriptionID = existingSub.ID + + if existingSub.Status != SubscriptionStatusActive { + if err := s.userSubRepo.UpdateStatus(txCtx, existingSub.ID, SubscriptionStatusActive); err != nil { + return nil, false, fmt.Errorf("update subscription status: %w", err) + } + } + if eventExpiresAt.After(existingSub.ExpiresAt) { + if err := s.userSubRepo.ExtendExpiry(txCtx, existingSub.ID, eventExpiresAt); err != nil { + return nil, false, fmt.Errorf("extend subscription: %w", err) + } + } + if input.Notes != "" { + newNotes := existingSub.Notes + if newNotes != "" { + newNotes += "\n" + } + newNotes += input.Notes + if err := s.userSubRepo.UpdateNotes(txCtx, existingSub.ID, newNotes); err != nil { + return nil, false, fmt.Errorf("update subscription notes: %w", err) + } + } + } else { + sub := &UserSubscription{ + UserID: input.UserID, + GroupID: input.GroupID, + StartsAt: now, + ExpiresAt: eventExpiresAt, + Status: SubscriptionStatusActive, + AssignedAt: now, + Notes: input.Notes, + CreatedAt: now, + UpdatedAt: now, + } + if input.AssignedBy > 0 { + sub.AssignedBy = &input.AssignedBy + } + if err := s.userSubRepo.Create(txCtx, sub); err != nil { + return nil, false, err + } + subscriptionID = sub.ID + } + + createdEvent, err := s.createQuotaEvent(txCtx, subscriptionID, group, input, now, eventExpiresAt) + if err != nil { + return nil, false, fmt.Errorf("create quota event: %w", err) + } + if reused { + if err := s.userSubRepo.RetireDepletedQuotaEventsOnAppend(txCtx, subscriptionID, createdEvent.ID, now); err != nil { + return nil, false, fmt.Errorf("retire depleted quota events: %w", err) + } + } + + if tx != nil { + if err := tx.Commit(); err != nil { + return nil, false, fmt.Errorf("commit transaction: %w", err) + } + } + + s.InvalidateSubCache(input.UserID, input.GroupID) + if s.billingCacheService != nil { + userID, groupID := input.UserID, input.GroupID + go func() { + cacheCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = s.billingCacheService.InvalidateSubscription(cacheCtx, userID, groupID) + }() + } + + sub, err := s.userSubRepo.GetByID(ctx, subscriptionID) + return sub, reused, err +} + // RevokeSubscription 撤销订阅 func (s *SubscriptionService) RevokeSubscription(ctx context.Context, subscriptionID int64) error { // 先获取订阅信息用于失效缓存 @@ -649,6 +831,9 @@ func (s *SubscriptionService) ExtendSubscription(ctx context.Context, subscripti if err != nil { return nil, ErrSubscriptionNotFound } + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + return nil, ErrTotalQuotaAdjustUnsupported + } // 限制调整天数范围 if days > MaxValidityDays { @@ -725,8 +910,13 @@ func (s *SubscriptionService) GetActiveSubscription(ctx context.Context, userID, if s.subCacheL1 != nil { if v, ok := s.subCacheL1.Get(key); ok { if sub, ok := v.(*UserSubscription); ok { - cp := *sub - return &cp, nil + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() && + sub.NextQuotaExpireAt != nil && !time.Now().Before(*sub.NextQuotaExpireAt) { + s.InvalidateSubCache(userID, groupID) + } else { + cp := *sub + return &cp, nil + } } } } @@ -861,6 +1051,9 @@ func (s *SubscriptionService) AdminResetQuota(ctx context.Context, subscriptionI if err != nil { return nil, err } + if sub.Group != nil && sub.Group.IsTotalQuotaSubscriptionType() { + return nil, ErrTotalQuotaResetUnsupported + } windowStart := startOfDay(time.Now()) if err := s.userSubRepo.ResetUsageWindows(ctx, sub.ID, resetDaily, resetWeekly, resetMonthly, windowStart); err != nil { return nil, err @@ -955,6 +1148,12 @@ func (s *SubscriptionService) EnsureWindowMaintenance(ctx context.Context, sub * // CheckUsageLimits 检查使用限额(返回错误如果超限) // 用于中间件的快速预检查,additionalCost 通常为 0 func (s *SubscriptionService) CheckUsageLimits(ctx context.Context, sub *UserSubscription, group *Group, additionalCost float64) error { + if group != nil && group.IsTotalQuotaSubscriptionType() { + if !sub.CheckTotalLimit(group, additionalCost) { + return ErrTotalLimitExceeded + } + return nil + } if !sub.CheckDailyLimit(group, additionalCost) { return ErrDailyLimitExceeded } @@ -981,6 +1180,12 @@ func (s *SubscriptionService) ValidateAndCheckLimits(sub *UserSubscription, grou if sub.IsExpired() { return false, ErrSubscriptionExpired } + if group != nil && group.IsTotalQuotaSubscriptionType() { + if sub.TotalRemainingUSD <= 0 { + return false, ErrTotalLimitExceeded + } + return false, nil + } // 2. 内存中修正过期窗口的用量,确保预检查不会误拒绝用户。 // 调用方随后同步推进 DB 窗口,并用回读快照重新校验。 @@ -1070,6 +1275,7 @@ type SubscriptionProgress struct { Daily *UsageWindowProgress `json:"daily,omitempty"` Weekly *UsageWindowProgress `json:"weekly,omitempty"` Monthly *UsageWindowProgress `json:"monthly,omitempty"` + Total *TotalQuotaProgress `json:"total,omitempty"` } // UsageWindowProgress 使用窗口进度 @@ -1083,6 +1289,16 @@ type UsageWindowProgress struct { ResetsInSeconds int64 `json:"resets_in_seconds"` } +type TotalQuotaProgress struct { + LimitUSD float64 `json:"limit_usd"` + UsedUSD float64 `json:"used_usd"` + RemainingUSD float64 `json:"remaining_usd"` + Percentage float64 `json:"percentage"` + NextQuotaExpireAt *time.Time `json:"next_quota_expire_at,omitempty"` + NextQuotaExpiresInSeconds int64 `json:"next_quota_expires_in_seconds"` + NextExpiringQuotaUSD float64 `json:"next_expiring_quota_usd"` +} + // GetSubscriptionProgress 获取订阅使用进度 func (s *SubscriptionService) GetSubscriptionProgress(ctx context.Context, subscriptionID int64) (*SubscriptionProgress, error) { sub, err := s.userSubRepo.GetByID(ctx, subscriptionID) @@ -1185,6 +1401,35 @@ func (s *SubscriptionService) calculateProgress(sub *UserSubscription, group *Gr } } + if group.IsTotalQuotaSubscriptionType() { + limit := sub.TotalLimitUSD + if limit <= 0 { + limit = sub.TotalUsedUSD + sub.TotalRemainingUSD + } + percentage := 0.0 + if limit > 0 { + percentage = (sub.TotalUsedUSD / limit) * 100 + if percentage > 100 { + percentage = 100 + } + } + progress.Total = &TotalQuotaProgress{ + LimitUSD: limit, + UsedUSD: sub.TotalUsedUSD, + RemainingUSD: sub.TotalRemainingUSD, + Percentage: percentage, + NextQuotaExpireAt: sub.NextQuotaExpireAt, + NextQuotaExpiresInSeconds: 0, + NextExpiringQuotaUSD: sub.NextExpiringQuotaUSD, + } + if sub.NextQuotaExpireAt != nil { + progress.Total.NextQuotaExpiresInSeconds = int64(time.Until(*sub.NextQuotaExpireAt).Seconds()) + if progress.Total.NextQuotaExpiresInSeconds < 0 { + progress.Total.NextQuotaExpiresInSeconds = 0 + } + } + } + return progress } @@ -1224,3 +1469,21 @@ func (s *SubscriptionService) ValidateSubscription(ctx context.Context, sub *Use } return nil } + +func (s *SubscriptionService) BuildTotalQuotaSpendSnapshot(ctx context.Context, sub *UserSubscription) (*TotalQuotaSpendSnapshot, error) { + if s == nil || sub == nil || sub.Group == nil || !sub.Group.IsTotalQuotaSubscriptionType() { + return nil, nil + } + provider, ok := s.userSubRepo.(totalQuotaSpendSnapshotProvider) + if !ok { + return nil, nil + } + snapshot, err := provider.GetQuotaSpendSnapshot(ctx, sub.ID, time.Now().UTC()) + if err != nil { + return nil, err + } + if snapshot == nil || len(snapshot.EventIDs) == 0 || snapshot.OverflowEventID == 0 { + return nil, ErrTotalLimitExceeded + } + return snapshot, nil +} diff --git a/backend/internal/service/user_subscription.go b/backend/internal/service/user_subscription.go index 12e3dd428a7..5a01075f368 100644 --- a/backend/internal/service/user_subscription.go +++ b/backend/internal/service/user_subscription.go @@ -20,6 +20,11 @@ type UserSubscription struct { DailyUsageUSD float64 WeeklyUsageUSD float64 MonthlyUsageUSD float64 + TotalLimitUSD float64 + TotalUsedUSD float64 + TotalRemainingUSD float64 + NextExpiringQuotaUSD float64 + NextQuotaExpireAt *time.Time AssignedBy *int64 AssignedAt time.Time @@ -32,6 +37,35 @@ type UserSubscription struct { User *User Group *Group AssignedByUser *User + QuotaEvents []UserSubscriptionQuotaEvent +} + +type UserSubscriptionQuotaEvent struct { + ID int64 + UserSubscriptionID int64 + QuotaTotalUSD float64 + QuotaUsedUSD float64 + StartsAt time.Time + ExpiresAt time.Time + SourceKind string + SourceRef string + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserSubscriptionQuotaSummary struct { + TotalLimitUSD float64 + TotalUsedUSD float64 + TotalRemainingUSD float64 + NextQuotaExpireAt *time.Time + NextExpiringQuotaUSD float64 +} + +type TotalQuotaSpendSnapshot struct { + SubscriptionID int64 + EventIDs []int64 + OverflowEventID int64 + TakenAt time.Time } func (s *UserSubscription) IsActive() bool { @@ -153,3 +187,10 @@ func (s *UserSubscription) CheckAllLimits(group *Group, additionalCost float64) monthly = s.CheckMonthlyLimit(group, additionalCost) return } + +func (s *UserSubscription) CheckTotalLimit(group *Group, additionalCost float64) bool { + if group == nil || !group.HasTotalLimit() { + return true + } + return s.TotalRemainingUSD >= additionalCost +} diff --git a/backend/internal/service/user_subscription_port.go b/backend/internal/service/user_subscription_port.go index eeee0275f07..0f402b5467c 100644 --- a/backend/internal/service/user_subscription_port.go +++ b/backend/internal/service/user_subscription_port.go @@ -34,6 +34,11 @@ type UserSubscriptionRepository interface { ResetWeeklyUsage(ctx context.Context, id int64, expectedWindowStart *time.Time, newWindowStart time.Time) error ResetMonthlyUsage(ctx context.Context, id int64, expectedWindowStart *time.Time, newWindowStart time.Time) error IncrementUsage(ctx context.Context, id int64, costUSD float64) error + CreateQuotaEvent(ctx context.Context, event *UserSubscriptionQuotaEvent) error + RetireDepletedQuotaEventsOnAppend(ctx context.Context, subscriptionID, keepEventID int64, retireAt time.Time) error + GetQuotaSummary(ctx context.Context, subscriptionID int64, now time.Time) (*UserSubscriptionQuotaSummary, error) + GetQuotaSummaryBatch(ctx context.Context, subscriptionIDs []int64, now time.Time) (map[int64]*UserSubscriptionQuotaSummary, error) BatchUpdateExpiredStatus(ctx context.Context) (int64, error) + DeleteExpiredQuotaEventsBatch(ctx context.Context, now time.Time, limit int) (int64, error) } diff --git a/backend/migrations/108_add_total_quota_subscription.sql b/backend/migrations/108_add_total_quota_subscription.sql new file mode 100644 index 00000000000..6d769b69426 --- /dev/null +++ b/backend/migrations/108_add_total_quota_subscription.sql @@ -0,0 +1,24 @@ +ALTER TABLE groups + ADD COLUMN IF NOT EXISTS total_limit_usd DECIMAL(20,8); + +CREATE TABLE IF NOT EXISTS user_subscription_quota_events ( + id BIGSERIAL PRIMARY KEY, + user_subscription_id BIGINT NOT NULL REFERENCES user_subscriptions(id) ON DELETE CASCADE, + quota_total_usd DECIMAL(20,10) NOT NULL, + quota_used_usd DECIMAL(20,10) NOT NULL DEFAULT 0, + starts_at TIMESTAMPTZ NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + source_kind VARCHAR(32) NOT NULL, + source_ref TEXT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_user_subscription_quota_events_subscription_id + ON user_subscription_quota_events(user_subscription_id); + +CREATE INDEX IF NOT EXISTS idx_user_subscription_quota_events_expires_at + ON user_subscription_quota_events(expires_at); + +CREATE INDEX IF NOT EXISTS idx_user_subscription_quota_events_subscription_expires + ON user_subscription_quota_events(user_subscription_id, expires_at, id); diff --git a/frontend/src/api/__tests__/admin.system.rollback.spec.ts b/frontend/src/api/__tests__/admin.system.rollback.spec.ts index 15d0989ffbb..09eab12b653 100644 --- a/frontend/src/api/__tests__/admin.system.rollback.spec.ts +++ b/frontend/src/api/__tests__/admin.system.rollback.spec.ts @@ -41,7 +41,11 @@ describe('admin system rollback API', () => { const result = await rollback('0.1.146') - expect(post).toHaveBeenCalledWith('/admin/system/rollback', { version: '0.1.146' }) + expect(post).toHaveBeenCalledWith( + '/admin/system/rollback', + { version: '0.1.146' }, + { timeout: 15 * 60 * 1000 } + ) expect(result.need_restart).toBe(true) }) @@ -50,6 +54,10 @@ describe('admin system rollback API', () => { await rollback() - expect(post).toHaveBeenCalledWith('/admin/system/rollback', undefined) + expect(post).toHaveBeenCalledWith( + '/admin/system/rollback', + undefined, + { timeout: 15 * 60 * 1000 } + ) }) }) diff --git a/frontend/src/components/account/__tests__/credentialsBuilder.spec.ts b/frontend/src/components/account/__tests__/credentialsBuilder.spec.ts index 6d4b59e2a74..828dbded42a 100644 --- a/frontend/src/components/account/__tests__/credentialsBuilder.spec.ts +++ b/frontend/src/components/account/__tests__/credentialsBuilder.spec.ts @@ -63,7 +63,6 @@ describe('applyInterceptWarmup', () => { expect('intercept_warmup_requests' in creds).toBe(false) }) }) - describe('applyAntigravityProjectID', () => { it('create + project id: trims and stores configured project fallback', () => { const creds: Record = { access_token: 'tok' } @@ -468,4 +467,3 @@ describe('plan_type helpers', () => { }) }) }) - diff --git a/frontend/src/components/common/SubscriptionProgressMini.vue b/frontend/src/components/common/SubscriptionProgressMini.vue index bba7aaf79fa..920ce7229df 100644 --- a/frontend/src/components/common/SubscriptionProgressMini.vue +++ b/frontend/src/components/common/SubscriptionProgressMini.vue @@ -72,6 +72,37 @@