feat: apikey支持5h/1d/7d速率控制

This commit is contained in:
shaw
2026-03-03 15:01:10 +08:00
parent b7df7ce5d5
commit a80ec5d8bb
33 changed files with 3715 additions and 83 deletions

View File

@@ -58,11 +58,12 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
promoCodeRepository := repository.NewPromoCodeRepository(client) promoCodeRepository := repository.NewPromoCodeRepository(client)
billingCache := repository.NewBillingCache(redisClient) billingCache := repository.NewBillingCache(redisClient)
userSubscriptionRepository := repository.NewUserSubscriptionRepository(client) userSubscriptionRepository := repository.NewUserSubscriptionRepository(client)
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, configConfig) apiKeyRepository := repository.NewAPIKeyRepository(client, db)
apiKeyRepository := repository.NewAPIKeyRepository(client) billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository, apiKeyRepository, configConfig)
userGroupRateRepository := repository.NewUserGroupRateRepository(db) userGroupRateRepository := repository.NewUserGroupRateRepository(db)
apiKeyCache := repository.NewAPIKeyCache(redisClient) apiKeyCache := repository.NewAPIKeyCache(redisClient)
apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig) apiKeyService := service.NewAPIKeyService(apiKeyRepository, userRepository, groupRepository, userSubscriptionRepository, userGroupRateRepository, apiKeyCache, configConfig)
apiKeyService.SetRateLimitCacheInvalidator(billingCache)
apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService) apiKeyAuthCacheInvalidator := service.ProvideAPIKeyAuthCacheInvalidator(apiKeyService)
promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator) promoService := service.NewPromoService(promoCodeRepository, userRepository, billingCacheService, client, apiKeyAuthCacheInvalidator)
subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig) subscriptionService := service.NewSubscriptionService(groupRepository, userSubscriptionRepository, billingCacheService, client, configConfig)

View File

@@ -42,7 +42,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second) subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second)
pricingSvc := service.NewPricingService(cfg, nil) pricingSvc := service.NewPricingService(cfg, nil)
emailQueueSvc := service.NewEmailQueueService(nil, 1) emailQueueSvc := service.NewEmailQueueService(nil, 1)
billingCacheSvc := service.NewBillingCacheService(nil, nil, nil, cfg) billingCacheSvc := service.NewBillingCacheService(nil, nil, nil, nil, cfg)
idempotencyCleanupSvc := service.NewIdempotencyCleanupService(nil, cfg) idempotencyCleanupSvc := service.NewIdempotencyCleanupService(nil, cfg)
schedulerSnapshotSvc := service.NewSchedulerSnapshotService(nil, nil, nil, nil, cfg) schedulerSnapshotSvc := service.NewSchedulerSnapshotService(nil, nil, nil, nil, cfg)
opsSystemLogSinkSvc := service.NewOpsSystemLogSink(nil) opsSystemLogSinkSvc := service.NewOpsSystemLogSink(nil)

View File

@@ -48,6 +48,24 @@ type APIKey struct {
QuotaUsed float64 `json:"quota_used,omitempty"` QuotaUsed float64 `json:"quota_used,omitempty"`
// Expiration time for this API key (null = never expires) // Expiration time for this API key (null = never expires)
ExpiresAt *time.Time `json:"expires_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"`
// Rate limit in USD per 5 hours (0 = unlimited)
RateLimit5h float64 `json:"rate_limit_5h,omitempty"`
// Rate limit in USD per day (0 = unlimited)
RateLimit1d float64 `json:"rate_limit_1d,omitempty"`
// Rate limit in USD per 7 days (0 = unlimited)
RateLimit7d float64 `json:"rate_limit_7d,omitempty"`
// Used amount in USD for the current 5h window
Usage5h float64 `json:"usage_5h,omitempty"`
// Used amount in USD for the current 1d window
Usage1d float64 `json:"usage_1d,omitempty"`
// Used amount in USD for the current 7d window
Usage7d float64 `json:"usage_7d,omitempty"`
// Start time of the current 5h rate limit window
Window5hStart *time.Time `json:"window_5h_start,omitempty"`
// Start time of the current 1d rate limit window
Window1dStart *time.Time `json:"window_1d_start,omitempty"`
// Start time of the current 7d rate limit window
Window7dStart *time.Time `json:"window_7d_start,omitempty"`
// Edges holds the relations/edges for other nodes in the graph. // Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the APIKeyQuery when eager-loading is set. // The values are being populated by the APIKeyQuery when eager-loading is set.
Edges APIKeyEdges `json:"edges"` Edges APIKeyEdges `json:"edges"`
@@ -105,13 +123,13 @@ func (*APIKey) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist: case apikey.FieldIPWhitelist, apikey.FieldIPBlacklist:
values[i] = new([]byte) values[i] = new([]byte)
case apikey.FieldQuota, apikey.FieldQuotaUsed: case apikey.FieldQuota, apikey.FieldQuotaUsed, apikey.FieldRateLimit5h, apikey.FieldRateLimit1d, apikey.FieldRateLimit7d, apikey.FieldUsage5h, apikey.FieldUsage1d, apikey.FieldUsage7d:
values[i] = new(sql.NullFloat64) values[i] = new(sql.NullFloat64)
case apikey.FieldID, apikey.FieldUserID, apikey.FieldGroupID: case apikey.FieldID, apikey.FieldUserID, apikey.FieldGroupID:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus: case apikey.FieldKey, apikey.FieldName, apikey.FieldStatus:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt: case apikey.FieldCreatedAt, apikey.FieldUpdatedAt, apikey.FieldDeletedAt, apikey.FieldLastUsedAt, apikey.FieldExpiresAt, apikey.FieldWindow5hStart, apikey.FieldWindow1dStart, apikey.FieldWindow7dStart:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
@@ -226,6 +244,63 @@ func (_m *APIKey) assignValues(columns []string, values []any) error {
_m.ExpiresAt = new(time.Time) _m.ExpiresAt = new(time.Time)
*_m.ExpiresAt = value.Time *_m.ExpiresAt = value.Time
} }
case apikey.FieldRateLimit5h:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_5h", values[i])
} else if value.Valid {
_m.RateLimit5h = value.Float64
}
case apikey.FieldRateLimit1d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_1d", values[i])
} else if value.Valid {
_m.RateLimit1d = value.Float64
}
case apikey.FieldRateLimit7d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field rate_limit_7d", values[i])
} else if value.Valid {
_m.RateLimit7d = value.Float64
}
case apikey.FieldUsage5h:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_5h", values[i])
} else if value.Valid {
_m.Usage5h = value.Float64
}
case apikey.FieldUsage1d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_1d", values[i])
} else if value.Valid {
_m.Usage1d = value.Float64
}
case apikey.FieldUsage7d:
if value, ok := values[i].(*sql.NullFloat64); !ok {
return fmt.Errorf("unexpected type %T for field usage_7d", values[i])
} else if value.Valid {
_m.Usage7d = value.Float64
}
case apikey.FieldWindow5hStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_5h_start", values[i])
} else if value.Valid {
_m.Window5hStart = new(time.Time)
*_m.Window5hStart = value.Time
}
case apikey.FieldWindow1dStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_1d_start", values[i])
} else if value.Valid {
_m.Window1dStart = new(time.Time)
*_m.Window1dStart = value.Time
}
case apikey.FieldWindow7dStart:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field window_7d_start", values[i])
} else if value.Valid {
_m.Window7dStart = new(time.Time)
*_m.Window7dStart = value.Time
}
default: default:
_m.selectValues.Set(columns[i], values[i]) _m.selectValues.Set(columns[i], values[i])
} }
@@ -326,6 +401,39 @@ func (_m *APIKey) String() string {
builder.WriteString("expires_at=") builder.WriteString("expires_at=")
builder.WriteString(v.Format(time.ANSIC)) builder.WriteString(v.Format(time.ANSIC))
} }
builder.WriteString(", ")
builder.WriteString("rate_limit_5h=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit5h))
builder.WriteString(", ")
builder.WriteString("rate_limit_1d=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit1d))
builder.WriteString(", ")
builder.WriteString("rate_limit_7d=")
builder.WriteString(fmt.Sprintf("%v", _m.RateLimit7d))
builder.WriteString(", ")
builder.WriteString("usage_5h=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage5h))
builder.WriteString(", ")
builder.WriteString("usage_1d=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage1d))
builder.WriteString(", ")
builder.WriteString("usage_7d=")
builder.WriteString(fmt.Sprintf("%v", _m.Usage7d))
builder.WriteString(", ")
if v := _m.Window5hStart; v != nil {
builder.WriteString("window_5h_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.Window1dStart; v != nil {
builder.WriteString("window_1d_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
if v := _m.Window7dStart; v != nil {
builder.WriteString("window_7d_start=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteByte(')') builder.WriteByte(')')
return builder.String() return builder.String()
} }

View File

@@ -43,6 +43,24 @@ const (
FieldQuotaUsed = "quota_used" FieldQuotaUsed = "quota_used"
// FieldExpiresAt holds the string denoting the expires_at field in the database. // FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at" FieldExpiresAt = "expires_at"
// FieldRateLimit5h holds the string denoting the rate_limit_5h field in the database.
FieldRateLimit5h = "rate_limit_5h"
// FieldRateLimit1d holds the string denoting the rate_limit_1d field in the database.
FieldRateLimit1d = "rate_limit_1d"
// FieldRateLimit7d holds the string denoting the rate_limit_7d field in the database.
FieldRateLimit7d = "rate_limit_7d"
// FieldUsage5h holds the string denoting the usage_5h field in the database.
FieldUsage5h = "usage_5h"
// FieldUsage1d holds the string denoting the usage_1d field in the database.
FieldUsage1d = "usage_1d"
// FieldUsage7d holds the string denoting the usage_7d field in the database.
FieldUsage7d = "usage_7d"
// FieldWindow5hStart holds the string denoting the window_5h_start field in the database.
FieldWindow5hStart = "window_5h_start"
// FieldWindow1dStart holds the string denoting the window_1d_start field in the database.
FieldWindow1dStart = "window_1d_start"
// FieldWindow7dStart holds the string denoting the window_7d_start field in the database.
FieldWindow7dStart = "window_7d_start"
// EdgeUser holds the string denoting the user edge name in mutations. // EdgeUser holds the string denoting the user edge name in mutations.
EdgeUser = "user" EdgeUser = "user"
// EdgeGroup holds the string denoting the group edge name in mutations. // EdgeGroup holds the string denoting the group edge name in mutations.
@@ -91,6 +109,15 @@ var Columns = []string{
FieldQuota, FieldQuota,
FieldQuotaUsed, FieldQuotaUsed,
FieldExpiresAt, FieldExpiresAt,
FieldRateLimit5h,
FieldRateLimit1d,
FieldRateLimit7d,
FieldUsage5h,
FieldUsage1d,
FieldUsage7d,
FieldWindow5hStart,
FieldWindow1dStart,
FieldWindow7dStart,
} }
// ValidColumn reports if the column name is valid (part of the table columns). // ValidColumn reports if the column name is valid (part of the table columns).
@@ -129,6 +156,18 @@ var (
DefaultQuota float64 DefaultQuota float64
// DefaultQuotaUsed holds the default value on creation for the "quota_used" field. // DefaultQuotaUsed holds the default value on creation for the "quota_used" field.
DefaultQuotaUsed float64 DefaultQuotaUsed float64
// DefaultRateLimit5h holds the default value on creation for the "rate_limit_5h" field.
DefaultRateLimit5h float64
// DefaultRateLimit1d holds the default value on creation for the "rate_limit_1d" field.
DefaultRateLimit1d float64
// DefaultRateLimit7d holds the default value on creation for the "rate_limit_7d" field.
DefaultRateLimit7d float64
// DefaultUsage5h holds the default value on creation for the "usage_5h" field.
DefaultUsage5h float64
// DefaultUsage1d holds the default value on creation for the "usage_1d" field.
DefaultUsage1d float64
// DefaultUsage7d holds the default value on creation for the "usage_7d" field.
DefaultUsage7d float64
) )
// OrderOption defines the ordering options for the APIKey queries. // OrderOption defines the ordering options for the APIKey queries.
@@ -199,6 +238,51 @@ func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc() return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
} }
// ByRateLimit5h orders the results by the rate_limit_5h field.
func ByRateLimit5h(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit5h, opts...).ToFunc()
}
// ByRateLimit1d orders the results by the rate_limit_1d field.
func ByRateLimit1d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit1d, opts...).ToFunc()
}
// ByRateLimit7d orders the results by the rate_limit_7d field.
func ByRateLimit7d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldRateLimit7d, opts...).ToFunc()
}
// ByUsage5h orders the results by the usage_5h field.
func ByUsage5h(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage5h, opts...).ToFunc()
}
// ByUsage1d orders the results by the usage_1d field.
func ByUsage1d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage1d, opts...).ToFunc()
}
// ByUsage7d orders the results by the usage_7d field.
func ByUsage7d(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUsage7d, opts...).ToFunc()
}
// ByWindow5hStart orders the results by the window_5h_start field.
func ByWindow5hStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow5hStart, opts...).ToFunc()
}
// ByWindow1dStart orders the results by the window_1d_start field.
func ByWindow1dStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow1dStart, opts...).ToFunc()
}
// ByWindow7dStart orders the results by the window_7d_start field.
func ByWindow7dStart(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldWindow7dStart, opts...).ToFunc()
}
// ByUserField orders the results by user field. // ByUserField orders the results by user field.
func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption { func ByUserField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) { return func(s *sql.Selector) {

View File

@@ -115,6 +115,51 @@ func ExpiresAt(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldExpiresAt, v)) return predicate.APIKey(sql.FieldEQ(FieldExpiresAt, v))
} }
// RateLimit5h applies equality check predicate on the "rate_limit_5h" field. It's identical to RateLimit5hEQ.
func RateLimit5h(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
}
// RateLimit1d applies equality check predicate on the "rate_limit_1d" field. It's identical to RateLimit1dEQ.
func RateLimit1d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
}
// RateLimit7d applies equality check predicate on the "rate_limit_7d" field. It's identical to RateLimit7dEQ.
func RateLimit7d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
}
// Usage5h applies equality check predicate on the "usage_5h" field. It's identical to Usage5hEQ.
func Usage5h(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
}
// Usage1d applies equality check predicate on the "usage_1d" field. It's identical to Usage1dEQ.
func Usage1d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
}
// Usage7d applies equality check predicate on the "usage_7d" field. It's identical to Usage7dEQ.
func Usage7d(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
}
// Window5hStart applies equality check predicate on the "window_5h_start" field. It's identical to Window5hStartEQ.
func Window5hStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
}
// Window1dStart applies equality check predicate on the "window_1d_start" field. It's identical to Window1dStartEQ.
func Window1dStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
}
// Window7dStart applies equality check predicate on the "window_7d_start" field. It's identical to Window7dStartEQ.
func Window7dStart(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field. // CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.APIKey { func CreatedAtEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v)) return predicate.APIKey(sql.FieldEQ(FieldCreatedAt, v))
@@ -690,6 +735,396 @@ func ExpiresAtNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldExpiresAt)) return predicate.APIKey(sql.FieldNotNull(FieldExpiresAt))
} }
// RateLimit5hEQ applies the EQ predicate on the "rate_limit_5h" field.
func RateLimit5hEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit5h, v))
}
// RateLimit5hNEQ applies the NEQ predicate on the "rate_limit_5h" field.
func RateLimit5hNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit5h, v))
}
// RateLimit5hIn applies the In predicate on the "rate_limit_5h" field.
func RateLimit5hIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit5h, vs...))
}
// RateLimit5hNotIn applies the NotIn predicate on the "rate_limit_5h" field.
func RateLimit5hNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit5h, vs...))
}
// RateLimit5hGT applies the GT predicate on the "rate_limit_5h" field.
func RateLimit5hGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit5h, v))
}
// RateLimit5hGTE applies the GTE predicate on the "rate_limit_5h" field.
func RateLimit5hGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit5h, v))
}
// RateLimit5hLT applies the LT predicate on the "rate_limit_5h" field.
func RateLimit5hLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit5h, v))
}
// RateLimit5hLTE applies the LTE predicate on the "rate_limit_5h" field.
func RateLimit5hLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit5h, v))
}
// RateLimit1dEQ applies the EQ predicate on the "rate_limit_1d" field.
func RateLimit1dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit1d, v))
}
// RateLimit1dNEQ applies the NEQ predicate on the "rate_limit_1d" field.
func RateLimit1dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit1d, v))
}
// RateLimit1dIn applies the In predicate on the "rate_limit_1d" field.
func RateLimit1dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit1d, vs...))
}
// RateLimit1dNotIn applies the NotIn predicate on the "rate_limit_1d" field.
func RateLimit1dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit1d, vs...))
}
// RateLimit1dGT applies the GT predicate on the "rate_limit_1d" field.
func RateLimit1dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit1d, v))
}
// RateLimit1dGTE applies the GTE predicate on the "rate_limit_1d" field.
func RateLimit1dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit1d, v))
}
// RateLimit1dLT applies the LT predicate on the "rate_limit_1d" field.
func RateLimit1dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit1d, v))
}
// RateLimit1dLTE applies the LTE predicate on the "rate_limit_1d" field.
func RateLimit1dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit1d, v))
}
// RateLimit7dEQ applies the EQ predicate on the "rate_limit_7d" field.
func RateLimit7dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldRateLimit7d, v))
}
// RateLimit7dNEQ applies the NEQ predicate on the "rate_limit_7d" field.
func RateLimit7dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldRateLimit7d, v))
}
// RateLimit7dIn applies the In predicate on the "rate_limit_7d" field.
func RateLimit7dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldRateLimit7d, vs...))
}
// RateLimit7dNotIn applies the NotIn predicate on the "rate_limit_7d" field.
func RateLimit7dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldRateLimit7d, vs...))
}
// RateLimit7dGT applies the GT predicate on the "rate_limit_7d" field.
func RateLimit7dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldRateLimit7d, v))
}
// RateLimit7dGTE applies the GTE predicate on the "rate_limit_7d" field.
func RateLimit7dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldRateLimit7d, v))
}
// RateLimit7dLT applies the LT predicate on the "rate_limit_7d" field.
func RateLimit7dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldRateLimit7d, v))
}
// RateLimit7dLTE applies the LTE predicate on the "rate_limit_7d" field.
func RateLimit7dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldRateLimit7d, v))
}
// Usage5hEQ applies the EQ predicate on the "usage_5h" field.
func Usage5hEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage5h, v))
}
// Usage5hNEQ applies the NEQ predicate on the "usage_5h" field.
func Usage5hNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage5h, v))
}
// Usage5hIn applies the In predicate on the "usage_5h" field.
func Usage5hIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage5h, vs...))
}
// Usage5hNotIn applies the NotIn predicate on the "usage_5h" field.
func Usage5hNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage5h, vs...))
}
// Usage5hGT applies the GT predicate on the "usage_5h" field.
func Usage5hGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage5h, v))
}
// Usage5hGTE applies the GTE predicate on the "usage_5h" field.
func Usage5hGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage5h, v))
}
// Usage5hLT applies the LT predicate on the "usage_5h" field.
func Usage5hLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage5h, v))
}
// Usage5hLTE applies the LTE predicate on the "usage_5h" field.
func Usage5hLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage5h, v))
}
// Usage1dEQ applies the EQ predicate on the "usage_1d" field.
func Usage1dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage1d, v))
}
// Usage1dNEQ applies the NEQ predicate on the "usage_1d" field.
func Usage1dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage1d, v))
}
// Usage1dIn applies the In predicate on the "usage_1d" field.
func Usage1dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage1d, vs...))
}
// Usage1dNotIn applies the NotIn predicate on the "usage_1d" field.
func Usage1dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage1d, vs...))
}
// Usage1dGT applies the GT predicate on the "usage_1d" field.
func Usage1dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage1d, v))
}
// Usage1dGTE applies the GTE predicate on the "usage_1d" field.
func Usage1dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage1d, v))
}
// Usage1dLT applies the LT predicate on the "usage_1d" field.
func Usage1dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage1d, v))
}
// Usage1dLTE applies the LTE predicate on the "usage_1d" field.
func Usage1dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage1d, v))
}
// Usage7dEQ applies the EQ predicate on the "usage_7d" field.
func Usage7dEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldUsage7d, v))
}
// Usage7dNEQ applies the NEQ predicate on the "usage_7d" field.
func Usage7dNEQ(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldUsage7d, v))
}
// Usage7dIn applies the In predicate on the "usage_7d" field.
func Usage7dIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldUsage7d, vs...))
}
// Usage7dNotIn applies the NotIn predicate on the "usage_7d" field.
func Usage7dNotIn(vs ...float64) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldUsage7d, vs...))
}
// Usage7dGT applies the GT predicate on the "usage_7d" field.
func Usage7dGT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldUsage7d, v))
}
// Usage7dGTE applies the GTE predicate on the "usage_7d" field.
func Usage7dGTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldUsage7d, v))
}
// Usage7dLT applies the LT predicate on the "usage_7d" field.
func Usage7dLT(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldUsage7d, v))
}
// Usage7dLTE applies the LTE predicate on the "usage_7d" field.
func Usage7dLTE(v float64) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldUsage7d, v))
}
// Window5hStartEQ applies the EQ predicate on the "window_5h_start" field.
func Window5hStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow5hStart, v))
}
// Window5hStartNEQ applies the NEQ predicate on the "window_5h_start" field.
func Window5hStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow5hStart, v))
}
// Window5hStartIn applies the In predicate on the "window_5h_start" field.
func Window5hStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow5hStart, vs...))
}
// Window5hStartNotIn applies the NotIn predicate on the "window_5h_start" field.
func Window5hStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow5hStart, vs...))
}
// Window5hStartGT applies the GT predicate on the "window_5h_start" field.
func Window5hStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow5hStart, v))
}
// Window5hStartGTE applies the GTE predicate on the "window_5h_start" field.
func Window5hStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow5hStart, v))
}
// Window5hStartLT applies the LT predicate on the "window_5h_start" field.
func Window5hStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow5hStart, v))
}
// Window5hStartLTE applies the LTE predicate on the "window_5h_start" field.
func Window5hStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow5hStart, v))
}
// Window5hStartIsNil applies the IsNil predicate on the "window_5h_start" field.
func Window5hStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow5hStart))
}
// Window5hStartNotNil applies the NotNil predicate on the "window_5h_start" field.
func Window5hStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow5hStart))
}
// Window1dStartEQ applies the EQ predicate on the "window_1d_start" field.
func Window1dStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow1dStart, v))
}
// Window1dStartNEQ applies the NEQ predicate on the "window_1d_start" field.
func Window1dStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow1dStart, v))
}
// Window1dStartIn applies the In predicate on the "window_1d_start" field.
func Window1dStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow1dStart, vs...))
}
// Window1dStartNotIn applies the NotIn predicate on the "window_1d_start" field.
func Window1dStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow1dStart, vs...))
}
// Window1dStartGT applies the GT predicate on the "window_1d_start" field.
func Window1dStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow1dStart, v))
}
// Window1dStartGTE applies the GTE predicate on the "window_1d_start" field.
func Window1dStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow1dStart, v))
}
// Window1dStartLT applies the LT predicate on the "window_1d_start" field.
func Window1dStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow1dStart, v))
}
// Window1dStartLTE applies the LTE predicate on the "window_1d_start" field.
func Window1dStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow1dStart, v))
}
// Window1dStartIsNil applies the IsNil predicate on the "window_1d_start" field.
func Window1dStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow1dStart))
}
// Window1dStartNotNil applies the NotNil predicate on the "window_1d_start" field.
func Window1dStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow1dStart))
}
// Window7dStartEQ applies the EQ predicate on the "window_7d_start" field.
func Window7dStartEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldEQ(FieldWindow7dStart, v))
}
// Window7dStartNEQ applies the NEQ predicate on the "window_7d_start" field.
func Window7dStartNEQ(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNEQ(FieldWindow7dStart, v))
}
// Window7dStartIn applies the In predicate on the "window_7d_start" field.
func Window7dStartIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldIn(FieldWindow7dStart, vs...))
}
// Window7dStartNotIn applies the NotIn predicate on the "window_7d_start" field.
func Window7dStartNotIn(vs ...time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldNotIn(FieldWindow7dStart, vs...))
}
// Window7dStartGT applies the GT predicate on the "window_7d_start" field.
func Window7dStartGT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGT(FieldWindow7dStart, v))
}
// Window7dStartGTE applies the GTE predicate on the "window_7d_start" field.
func Window7dStartGTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldGTE(FieldWindow7dStart, v))
}
// Window7dStartLT applies the LT predicate on the "window_7d_start" field.
func Window7dStartLT(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLT(FieldWindow7dStart, v))
}
// Window7dStartLTE applies the LTE predicate on the "window_7d_start" field.
func Window7dStartLTE(v time.Time) predicate.APIKey {
return predicate.APIKey(sql.FieldLTE(FieldWindow7dStart, v))
}
// Window7dStartIsNil applies the IsNil predicate on the "window_7d_start" field.
func Window7dStartIsNil() predicate.APIKey {
return predicate.APIKey(sql.FieldIsNull(FieldWindow7dStart))
}
// Window7dStartNotNil applies the NotNil predicate on the "window_7d_start" field.
func Window7dStartNotNil() predicate.APIKey {
return predicate.APIKey(sql.FieldNotNull(FieldWindow7dStart))
}
// HasUser applies the HasEdge predicate on the "user" edge. // HasUser applies the HasEdge predicate on the "user" edge.
func HasUser() predicate.APIKey { func HasUser() predicate.APIKey {
return predicate.APIKey(func(s *sql.Selector) { return predicate.APIKey(func(s *sql.Selector) {

View File

@@ -181,6 +181,132 @@ func (_c *APIKeyCreate) SetNillableExpiresAt(v *time.Time) *APIKeyCreate {
return _c return _c
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_c *APIKeyCreate) SetRateLimit5h(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit5h(v)
return _c
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit5h(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit5h(*v)
}
return _c
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_c *APIKeyCreate) SetRateLimit1d(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit1d(v)
return _c
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit1d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit1d(*v)
}
return _c
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_c *APIKeyCreate) SetRateLimit7d(v float64) *APIKeyCreate {
_c.mutation.SetRateLimit7d(v)
return _c
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableRateLimit7d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetRateLimit7d(*v)
}
return _c
}
// SetUsage5h sets the "usage_5h" field.
func (_c *APIKeyCreate) SetUsage5h(v float64) *APIKeyCreate {
_c.mutation.SetUsage5h(v)
return _c
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage5h(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage5h(*v)
}
return _c
}
// SetUsage1d sets the "usage_1d" field.
func (_c *APIKeyCreate) SetUsage1d(v float64) *APIKeyCreate {
_c.mutation.SetUsage1d(v)
return _c
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage1d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage1d(*v)
}
return _c
}
// SetUsage7d sets the "usage_7d" field.
func (_c *APIKeyCreate) SetUsage7d(v float64) *APIKeyCreate {
_c.mutation.SetUsage7d(v)
return _c
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableUsage7d(v *float64) *APIKeyCreate {
if v != nil {
_c.SetUsage7d(*v)
}
return _c
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_c *APIKeyCreate) SetWindow5hStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow5hStart(v)
return _c
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow5hStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow5hStart(*v)
}
return _c
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_c *APIKeyCreate) SetWindow1dStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow1dStart(v)
return _c
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow1dStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow1dStart(*v)
}
return _c
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_c *APIKeyCreate) SetWindow7dStart(v time.Time) *APIKeyCreate {
_c.mutation.SetWindow7dStart(v)
return _c
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_c *APIKeyCreate) SetNillableWindow7dStart(v *time.Time) *APIKeyCreate {
if v != nil {
_c.SetWindow7dStart(*v)
}
return _c
}
// SetUser sets the "user" edge to the User entity. // SetUser sets the "user" edge to the User entity.
func (_c *APIKeyCreate) SetUser(v *User) *APIKeyCreate { func (_c *APIKeyCreate) SetUser(v *User) *APIKeyCreate {
return _c.SetUserID(v.ID) return _c.SetUserID(v.ID)
@@ -269,6 +395,30 @@ func (_c *APIKeyCreate) defaults() error {
v := apikey.DefaultQuotaUsed v := apikey.DefaultQuotaUsed
_c.mutation.SetQuotaUsed(v) _c.mutation.SetQuotaUsed(v)
} }
if _, ok := _c.mutation.RateLimit5h(); !ok {
v := apikey.DefaultRateLimit5h
_c.mutation.SetRateLimit5h(v)
}
if _, ok := _c.mutation.RateLimit1d(); !ok {
v := apikey.DefaultRateLimit1d
_c.mutation.SetRateLimit1d(v)
}
if _, ok := _c.mutation.RateLimit7d(); !ok {
v := apikey.DefaultRateLimit7d
_c.mutation.SetRateLimit7d(v)
}
if _, ok := _c.mutation.Usage5h(); !ok {
v := apikey.DefaultUsage5h
_c.mutation.SetUsage5h(v)
}
if _, ok := _c.mutation.Usage1d(); !ok {
v := apikey.DefaultUsage1d
_c.mutation.SetUsage1d(v)
}
if _, ok := _c.mutation.Usage7d(); !ok {
v := apikey.DefaultUsage7d
_c.mutation.SetUsage7d(v)
}
return nil return nil
} }
@@ -313,6 +463,24 @@ func (_c *APIKeyCreate) check() error {
if _, ok := _c.mutation.QuotaUsed(); !ok { if _, ok := _c.mutation.QuotaUsed(); !ok {
return &ValidationError{Name: "quota_used", err: errors.New(`ent: missing required field "APIKey.quota_used"`)} return &ValidationError{Name: "quota_used", err: errors.New(`ent: missing required field "APIKey.quota_used"`)}
} }
if _, ok := _c.mutation.RateLimit5h(); !ok {
return &ValidationError{Name: "rate_limit_5h", err: errors.New(`ent: missing required field "APIKey.rate_limit_5h"`)}
}
if _, ok := _c.mutation.RateLimit1d(); !ok {
return &ValidationError{Name: "rate_limit_1d", err: errors.New(`ent: missing required field "APIKey.rate_limit_1d"`)}
}
if _, ok := _c.mutation.RateLimit7d(); !ok {
return &ValidationError{Name: "rate_limit_7d", err: errors.New(`ent: missing required field "APIKey.rate_limit_7d"`)}
}
if _, ok := _c.mutation.Usage5h(); !ok {
return &ValidationError{Name: "usage_5h", err: errors.New(`ent: missing required field "APIKey.usage_5h"`)}
}
if _, ok := _c.mutation.Usage1d(); !ok {
return &ValidationError{Name: "usage_1d", err: errors.New(`ent: missing required field "APIKey.usage_1d"`)}
}
if _, ok := _c.mutation.Usage7d(); !ok {
return &ValidationError{Name: "usage_7d", err: errors.New(`ent: missing required field "APIKey.usage_7d"`)}
}
if len(_c.mutation.UserIDs()) == 0 { if len(_c.mutation.UserIDs()) == 0 {
return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "APIKey.user"`)} return &ValidationError{Name: "user", err: errors.New(`ent: missing required edge "APIKey.user"`)}
} }
@@ -391,6 +559,42 @@ func (_c *APIKeyCreate) createSpec() (*APIKey, *sqlgraph.CreateSpec) {
_spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value) _spec.SetField(apikey.FieldExpiresAt, field.TypeTime, value)
_node.ExpiresAt = &value _node.ExpiresAt = &value
} }
if value, ok := _c.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
_node.RateLimit5h = value
}
if value, ok := _c.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
_node.RateLimit1d = value
}
if value, ok := _c.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
_node.RateLimit7d = value
}
if value, ok := _c.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
_node.Usage5h = value
}
if value, ok := _c.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
_node.Usage1d = value
}
if value, ok := _c.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
_node.Usage7d = value
}
if value, ok := _c.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
_node.Window5hStart = &value
}
if value, ok := _c.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
_node.Window1dStart = &value
}
if value, ok := _c.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
_node.Window7dStart = &value
}
if nodes := _c.mutation.UserIDs(); len(nodes) > 0 { if nodes := _c.mutation.UserIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@@ -697,6 +901,168 @@ func (u *APIKeyUpsert) ClearExpiresAt() *APIKeyUpsert {
return u return u
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsert) SetRateLimit5h(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit5h, v)
return u
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit5h() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit5h)
return u
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsert) AddRateLimit5h(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit5h, v)
return u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsert) SetRateLimit1d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit1d, v)
return u
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit1d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit1d)
return u
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsert) AddRateLimit1d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit1d, v)
return u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsert) SetRateLimit7d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldRateLimit7d, v)
return u
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateRateLimit7d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldRateLimit7d)
return u
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsert) AddRateLimit7d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldRateLimit7d, v)
return u
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsert) SetUsage5h(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage5h, v)
return u
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage5h() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage5h)
return u
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsert) AddUsage5h(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage5h, v)
return u
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsert) SetUsage1d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage1d, v)
return u
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage1d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage1d)
return u
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsert) AddUsage1d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage1d, v)
return u
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsert) SetUsage7d(v float64) *APIKeyUpsert {
u.Set(apikey.FieldUsage7d, v)
return u
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateUsage7d() *APIKeyUpsert {
u.SetExcluded(apikey.FieldUsage7d)
return u
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsert) AddUsage7d(v float64) *APIKeyUpsert {
u.Add(apikey.FieldUsage7d, v)
return u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsert) SetWindow5hStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow5hStart, v)
return u
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow5hStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow5hStart)
return u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsert) ClearWindow5hStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow5hStart)
return u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsert) SetWindow1dStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow1dStart, v)
return u
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow1dStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow1dStart)
return u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsert) ClearWindow1dStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow1dStart)
return u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsert) SetWindow7dStart(v time.Time) *APIKeyUpsert {
u.Set(apikey.FieldWindow7dStart, v)
return u
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsert) UpdateWindow7dStart() *APIKeyUpsert {
u.SetExcluded(apikey.FieldWindow7dStart)
return u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsert) ClearWindow7dStart() *APIKeyUpsert {
u.SetNull(apikey.FieldWindow7dStart)
return u
}
// UpdateNewValues updates the mutable fields using the new values that were set on create. // UpdateNewValues updates the mutable fields using the new values that were set on create.
// Using this option is equivalent to using: // Using this option is equivalent to using:
// //
@@ -980,6 +1346,195 @@ func (u *APIKeyUpsertOne) ClearExpiresAt() *APIKeyUpsertOne {
}) })
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsertOne) SetRateLimit5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit5h(v)
})
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsertOne) AddRateLimit5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit5h(v)
})
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit5h() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit5h()
})
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsertOne) SetRateLimit1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit1d(v)
})
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsertOne) AddRateLimit1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit1d(v)
})
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit1d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit1d()
})
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsertOne) SetRateLimit7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit7d(v)
})
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsertOne) AddRateLimit7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit7d(v)
})
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateRateLimit7d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit7d()
})
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsertOne) SetUsage5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage5h(v)
})
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsertOne) AddUsage5h(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage5h(v)
})
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage5h() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage5h()
})
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsertOne) SetUsage1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage1d(v)
})
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsertOne) AddUsage1d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage1d(v)
})
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage1d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage1d()
})
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsertOne) SetUsage7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage7d(v)
})
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsertOne) AddUsage7d(v float64) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage7d(v)
})
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateUsage7d() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage7d()
})
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsertOne) SetWindow5hStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow5hStart(v)
})
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow5hStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow5hStart()
})
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsertOne) ClearWindow5hStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow5hStart()
})
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsertOne) SetWindow1dStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow1dStart(v)
})
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow1dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow1dStart()
})
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsertOne) ClearWindow1dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow1dStart()
})
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsertOne) SetWindow7dStart(v time.Time) *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow7dStart(v)
})
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsertOne) UpdateWindow7dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow7dStart()
})
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsertOne) ClearWindow7dStart() *APIKeyUpsertOne {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow7dStart()
})
}
// Exec executes the query. // Exec executes the query.
func (u *APIKeyUpsertOne) Exec(ctx context.Context) error { func (u *APIKeyUpsertOne) Exec(ctx context.Context) error {
if len(u.create.conflict) == 0 { if len(u.create.conflict) == 0 {
@@ -1429,6 +1984,195 @@ func (u *APIKeyUpsertBulk) ClearExpiresAt() *APIKeyUpsertBulk {
}) })
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (u *APIKeyUpsertBulk) SetRateLimit5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit5h(v)
})
}
// AddRateLimit5h adds v to the "rate_limit_5h" field.
func (u *APIKeyUpsertBulk) AddRateLimit5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit5h(v)
})
}
// UpdateRateLimit5h sets the "rate_limit_5h" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit5h() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit5h()
})
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (u *APIKeyUpsertBulk) SetRateLimit1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit1d(v)
})
}
// AddRateLimit1d adds v to the "rate_limit_1d" field.
func (u *APIKeyUpsertBulk) AddRateLimit1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit1d(v)
})
}
// UpdateRateLimit1d sets the "rate_limit_1d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit1d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit1d()
})
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (u *APIKeyUpsertBulk) SetRateLimit7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetRateLimit7d(v)
})
}
// AddRateLimit7d adds v to the "rate_limit_7d" field.
func (u *APIKeyUpsertBulk) AddRateLimit7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddRateLimit7d(v)
})
}
// UpdateRateLimit7d sets the "rate_limit_7d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateRateLimit7d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateRateLimit7d()
})
}
// SetUsage5h sets the "usage_5h" field.
func (u *APIKeyUpsertBulk) SetUsage5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage5h(v)
})
}
// AddUsage5h adds v to the "usage_5h" field.
func (u *APIKeyUpsertBulk) AddUsage5h(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage5h(v)
})
}
// UpdateUsage5h sets the "usage_5h" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage5h() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage5h()
})
}
// SetUsage1d sets the "usage_1d" field.
func (u *APIKeyUpsertBulk) SetUsage1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage1d(v)
})
}
// AddUsage1d adds v to the "usage_1d" field.
func (u *APIKeyUpsertBulk) AddUsage1d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage1d(v)
})
}
// UpdateUsage1d sets the "usage_1d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage1d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage1d()
})
}
// SetUsage7d sets the "usage_7d" field.
func (u *APIKeyUpsertBulk) SetUsage7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetUsage7d(v)
})
}
// AddUsage7d adds v to the "usage_7d" field.
func (u *APIKeyUpsertBulk) AddUsage7d(v float64) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.AddUsage7d(v)
})
}
// UpdateUsage7d sets the "usage_7d" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateUsage7d() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateUsage7d()
})
}
// SetWindow5hStart sets the "window_5h_start" field.
func (u *APIKeyUpsertBulk) SetWindow5hStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow5hStart(v)
})
}
// UpdateWindow5hStart sets the "window_5h_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow5hStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow5hStart()
})
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (u *APIKeyUpsertBulk) ClearWindow5hStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow5hStart()
})
}
// SetWindow1dStart sets the "window_1d_start" field.
func (u *APIKeyUpsertBulk) SetWindow1dStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow1dStart(v)
})
}
// UpdateWindow1dStart sets the "window_1d_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow1dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow1dStart()
})
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (u *APIKeyUpsertBulk) ClearWindow1dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow1dStart()
})
}
// SetWindow7dStart sets the "window_7d_start" field.
func (u *APIKeyUpsertBulk) SetWindow7dStart(v time.Time) *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.SetWindow7dStart(v)
})
}
// UpdateWindow7dStart sets the "window_7d_start" field to the value that was provided on create.
func (u *APIKeyUpsertBulk) UpdateWindow7dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.UpdateWindow7dStart()
})
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (u *APIKeyUpsertBulk) ClearWindow7dStart() *APIKeyUpsertBulk {
return u.Update(func(s *APIKeyUpsert) {
s.ClearWindow7dStart()
})
}
// Exec executes the query. // Exec executes the query.
func (u *APIKeyUpsertBulk) Exec(ctx context.Context) error { func (u *APIKeyUpsertBulk) Exec(ctx context.Context) error {
if u.create.err != nil { if u.create.err != nil {

View File

@@ -252,6 +252,192 @@ func (_u *APIKeyUpdate) ClearExpiresAt() *APIKeyUpdate {
return _u return _u
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_u *APIKeyUpdate) SetRateLimit5h(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit5h()
_u.mutation.SetRateLimit5h(v)
return _u
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit5h(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit5h(*v)
}
return _u
}
// AddRateLimit5h adds value to the "rate_limit_5h" field.
func (_u *APIKeyUpdate) AddRateLimit5h(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit5h(v)
return _u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_u *APIKeyUpdate) SetRateLimit1d(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit1d()
_u.mutation.SetRateLimit1d(v)
return _u
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit1d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit1d(*v)
}
return _u
}
// AddRateLimit1d adds value to the "rate_limit_1d" field.
func (_u *APIKeyUpdate) AddRateLimit1d(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit1d(v)
return _u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_u *APIKeyUpdate) SetRateLimit7d(v float64) *APIKeyUpdate {
_u.mutation.ResetRateLimit7d()
_u.mutation.SetRateLimit7d(v)
return _u
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableRateLimit7d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetRateLimit7d(*v)
}
return _u
}
// AddRateLimit7d adds value to the "rate_limit_7d" field.
func (_u *APIKeyUpdate) AddRateLimit7d(v float64) *APIKeyUpdate {
_u.mutation.AddRateLimit7d(v)
return _u
}
// SetUsage5h sets the "usage_5h" field.
func (_u *APIKeyUpdate) SetUsage5h(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage5h()
_u.mutation.SetUsage5h(v)
return _u
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage5h(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage5h(*v)
}
return _u
}
// AddUsage5h adds value to the "usage_5h" field.
func (_u *APIKeyUpdate) AddUsage5h(v float64) *APIKeyUpdate {
_u.mutation.AddUsage5h(v)
return _u
}
// SetUsage1d sets the "usage_1d" field.
func (_u *APIKeyUpdate) SetUsage1d(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage1d()
_u.mutation.SetUsage1d(v)
return _u
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage1d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage1d(*v)
}
return _u
}
// AddUsage1d adds value to the "usage_1d" field.
func (_u *APIKeyUpdate) AddUsage1d(v float64) *APIKeyUpdate {
_u.mutation.AddUsage1d(v)
return _u
}
// SetUsage7d sets the "usage_7d" field.
func (_u *APIKeyUpdate) SetUsage7d(v float64) *APIKeyUpdate {
_u.mutation.ResetUsage7d()
_u.mutation.SetUsage7d(v)
return _u
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableUsage7d(v *float64) *APIKeyUpdate {
if v != nil {
_u.SetUsage7d(*v)
}
return _u
}
// AddUsage7d adds value to the "usage_7d" field.
func (_u *APIKeyUpdate) AddUsage7d(v float64) *APIKeyUpdate {
_u.mutation.AddUsage7d(v)
return _u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_u *APIKeyUpdate) SetWindow5hStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow5hStart(v)
return _u
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow5hStart(*v)
}
return _u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (_u *APIKeyUpdate) ClearWindow5hStart() *APIKeyUpdate {
_u.mutation.ClearWindow5hStart()
return _u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_u *APIKeyUpdate) SetWindow1dStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow1dStart(v)
return _u
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow1dStart(*v)
}
return _u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (_u *APIKeyUpdate) ClearWindow1dStart() *APIKeyUpdate {
_u.mutation.ClearWindow1dStart()
return _u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_u *APIKeyUpdate) SetWindow7dStart(v time.Time) *APIKeyUpdate {
_u.mutation.SetWindow7dStart(v)
return _u
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_u *APIKeyUpdate) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdate {
if v != nil {
_u.SetWindow7dStart(*v)
}
return _u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (_u *APIKeyUpdate) ClearWindow7dStart() *APIKeyUpdate {
_u.mutation.ClearWindow7dStart()
return _u
}
// SetUser sets the "user" edge to the User entity. // SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate { func (_u *APIKeyUpdate) SetUser(v *User) *APIKeyUpdate {
return _u.SetUserID(v.ID) return _u.SetUserID(v.ID)
@@ -456,6 +642,60 @@ func (_u *APIKeyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.ExpiresAtCleared() { if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime) _spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
} }
if value, ok := _u.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage5h(); ok {
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage1d(); ok {
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage7d(); ok {
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
}
if _u.mutation.Window5hStartCleared() {
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
}
if value, ok := _u.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
}
if _u.mutation.Window1dStartCleared() {
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
}
if value, ok := _u.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
}
if _u.mutation.Window7dStartCleared() {
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
}
if _u.mutation.UserCleared() { if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,
@@ -799,6 +1039,192 @@ func (_u *APIKeyUpdateOne) ClearExpiresAt() *APIKeyUpdateOne {
return _u return _u
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (_u *APIKeyUpdateOne) SetRateLimit5h(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit5h()
_u.mutation.SetRateLimit5h(v)
return _u
}
// SetNillableRateLimit5h sets the "rate_limit_5h" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit5h(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit5h(*v)
}
return _u
}
// AddRateLimit5h adds value to the "rate_limit_5h" field.
func (_u *APIKeyUpdateOne) AddRateLimit5h(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit5h(v)
return _u
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (_u *APIKeyUpdateOne) SetRateLimit1d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit1d()
_u.mutation.SetRateLimit1d(v)
return _u
}
// SetNillableRateLimit1d sets the "rate_limit_1d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit1d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit1d(*v)
}
return _u
}
// AddRateLimit1d adds value to the "rate_limit_1d" field.
func (_u *APIKeyUpdateOne) AddRateLimit1d(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit1d(v)
return _u
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (_u *APIKeyUpdateOne) SetRateLimit7d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetRateLimit7d()
_u.mutation.SetRateLimit7d(v)
return _u
}
// SetNillableRateLimit7d sets the "rate_limit_7d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableRateLimit7d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetRateLimit7d(*v)
}
return _u
}
// AddRateLimit7d adds value to the "rate_limit_7d" field.
func (_u *APIKeyUpdateOne) AddRateLimit7d(v float64) *APIKeyUpdateOne {
_u.mutation.AddRateLimit7d(v)
return _u
}
// SetUsage5h sets the "usage_5h" field.
func (_u *APIKeyUpdateOne) SetUsage5h(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage5h()
_u.mutation.SetUsage5h(v)
return _u
}
// SetNillableUsage5h sets the "usage_5h" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage5h(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage5h(*v)
}
return _u
}
// AddUsage5h adds value to the "usage_5h" field.
func (_u *APIKeyUpdateOne) AddUsage5h(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage5h(v)
return _u
}
// SetUsage1d sets the "usage_1d" field.
func (_u *APIKeyUpdateOne) SetUsage1d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage1d()
_u.mutation.SetUsage1d(v)
return _u
}
// SetNillableUsage1d sets the "usage_1d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage1d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage1d(*v)
}
return _u
}
// AddUsage1d adds value to the "usage_1d" field.
func (_u *APIKeyUpdateOne) AddUsage1d(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage1d(v)
return _u
}
// SetUsage7d sets the "usage_7d" field.
func (_u *APIKeyUpdateOne) SetUsage7d(v float64) *APIKeyUpdateOne {
_u.mutation.ResetUsage7d()
_u.mutation.SetUsage7d(v)
return _u
}
// SetNillableUsage7d sets the "usage_7d" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableUsage7d(v *float64) *APIKeyUpdateOne {
if v != nil {
_u.SetUsage7d(*v)
}
return _u
}
// AddUsage7d adds value to the "usage_7d" field.
func (_u *APIKeyUpdateOne) AddUsage7d(v float64) *APIKeyUpdateOne {
_u.mutation.AddUsage7d(v)
return _u
}
// SetWindow5hStart sets the "window_5h_start" field.
func (_u *APIKeyUpdateOne) SetWindow5hStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow5hStart(v)
return _u
}
// SetNillableWindow5hStart sets the "window_5h_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow5hStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow5hStart(*v)
}
return _u
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (_u *APIKeyUpdateOne) ClearWindow5hStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow5hStart()
return _u
}
// SetWindow1dStart sets the "window_1d_start" field.
func (_u *APIKeyUpdateOne) SetWindow1dStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow1dStart(v)
return _u
}
// SetNillableWindow1dStart sets the "window_1d_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow1dStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow1dStart(*v)
}
return _u
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (_u *APIKeyUpdateOne) ClearWindow1dStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow1dStart()
return _u
}
// SetWindow7dStart sets the "window_7d_start" field.
func (_u *APIKeyUpdateOne) SetWindow7dStart(v time.Time) *APIKeyUpdateOne {
_u.mutation.SetWindow7dStart(v)
return _u
}
// SetNillableWindow7dStart sets the "window_7d_start" field if the given value is not nil.
func (_u *APIKeyUpdateOne) SetNillableWindow7dStart(v *time.Time) *APIKeyUpdateOne {
if v != nil {
_u.SetWindow7dStart(*v)
}
return _u
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (_u *APIKeyUpdateOne) ClearWindow7dStart() *APIKeyUpdateOne {
_u.mutation.ClearWindow7dStart()
return _u
}
// SetUser sets the "user" edge to the User entity. // SetUser sets the "user" edge to the User entity.
func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne { func (_u *APIKeyUpdateOne) SetUser(v *User) *APIKeyUpdateOne {
return _u.SetUserID(v.ID) return _u.SetUserID(v.ID)
@@ -1033,6 +1459,60 @@ func (_u *APIKeyUpdateOne) sqlSave(ctx context.Context) (_node *APIKey, err erro
if _u.mutation.ExpiresAtCleared() { if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(apikey.FieldExpiresAt, field.TypeTime) _spec.ClearField(apikey.FieldExpiresAt, field.TypeTime)
} }
if value, ok := _u.mutation.RateLimit5h(); ok {
_spec.SetField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit5h(); ok {
_spec.AddField(apikey.FieldRateLimit5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit1d(); ok {
_spec.SetField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit1d(); ok {
_spec.AddField(apikey.FieldRateLimit1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.RateLimit7d(); ok {
_spec.SetField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedRateLimit7d(); ok {
_spec.AddField(apikey.FieldRateLimit7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage5h(); ok {
_spec.SetField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage5h(); ok {
_spec.AddField(apikey.FieldUsage5h, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage1d(); ok {
_spec.SetField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage1d(); ok {
_spec.AddField(apikey.FieldUsage1d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Usage7d(); ok {
_spec.SetField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.AddedUsage7d(); ok {
_spec.AddField(apikey.FieldUsage7d, field.TypeFloat64, value)
}
if value, ok := _u.mutation.Window5hStart(); ok {
_spec.SetField(apikey.FieldWindow5hStart, field.TypeTime, value)
}
if _u.mutation.Window5hStartCleared() {
_spec.ClearField(apikey.FieldWindow5hStart, field.TypeTime)
}
if value, ok := _u.mutation.Window1dStart(); ok {
_spec.SetField(apikey.FieldWindow1dStart, field.TypeTime, value)
}
if _u.mutation.Window1dStartCleared() {
_spec.ClearField(apikey.FieldWindow1dStart, field.TypeTime)
}
if value, ok := _u.mutation.Window7dStart(); ok {
_spec.SetField(apikey.FieldWindow7dStart, field.TypeTime, value)
}
if _u.mutation.Window7dStartCleared() {
_spec.ClearField(apikey.FieldWindow7dStart, field.TypeTime)
}
if _u.mutation.UserCleared() { if _u.mutation.UserCleared() {
edge := &sqlgraph.EdgeSpec{ edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O, Rel: sqlgraph.M2O,

View File

@@ -24,6 +24,15 @@ var (
{Name: "quota", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "quota", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "quota_used", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "quota_used", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "expires_at", Type: field.TypeTime, Nullable: true}, {Name: "expires_at", Type: field.TypeTime, Nullable: true},
{Name: "rate_limit_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "rate_limit_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "rate_limit_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_5h", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_1d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "usage_7d", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}},
{Name: "window_5h_start", Type: field.TypeTime, Nullable: true},
{Name: "window_1d_start", Type: field.TypeTime, Nullable: true},
{Name: "window_7d_start", Type: field.TypeTime, Nullable: true},
{Name: "group_id", Type: field.TypeInt64, Nullable: true}, {Name: "group_id", Type: field.TypeInt64, Nullable: true},
{Name: "user_id", Type: field.TypeInt64}, {Name: "user_id", Type: field.TypeInt64},
} }
@@ -35,13 +44,13 @@ var (
ForeignKeys: []*schema.ForeignKey{ ForeignKeys: []*schema.ForeignKey{
{ {
Symbol: "api_keys_groups_api_keys", Symbol: "api_keys_groups_api_keys",
Columns: []*schema.Column{APIKeysColumns[13]}, Columns: []*schema.Column{APIKeysColumns[22]},
RefColumns: []*schema.Column{GroupsColumns[0]}, RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.SetNull, OnDelete: schema.SetNull,
}, },
{ {
Symbol: "api_keys_users_api_keys", Symbol: "api_keys_users_api_keys",
Columns: []*schema.Column{APIKeysColumns[14]}, Columns: []*schema.Column{APIKeysColumns[23]},
RefColumns: []*schema.Column{UsersColumns[0]}, RefColumns: []*schema.Column{UsersColumns[0]},
OnDelete: schema.NoAction, OnDelete: schema.NoAction,
}, },
@@ -50,12 +59,12 @@ var (
{ {
Name: "apikey_user_id", Name: "apikey_user_id",
Unique: false, Unique: false,
Columns: []*schema.Column{APIKeysColumns[14]}, Columns: []*schema.Column{APIKeysColumns[23]},
}, },
{ {
Name: "apikey_group_id", Name: "apikey_group_id",
Unique: false, Unique: false,
Columns: []*schema.Column{APIKeysColumns[13]}, Columns: []*schema.Column{APIKeysColumns[22]},
}, },
{ {
Name: "apikey_status", Name: "apikey_status",

View File

@@ -91,6 +91,21 @@ type APIKeyMutation struct {
quota_used *float64 quota_used *float64
addquota_used *float64 addquota_used *float64
expires_at *time.Time expires_at *time.Time
rate_limit_5h *float64
addrate_limit_5h *float64
rate_limit_1d *float64
addrate_limit_1d *float64
rate_limit_7d *float64
addrate_limit_7d *float64
usage_5h *float64
addusage_5h *float64
usage_1d *float64
addusage_1d *float64
usage_7d *float64
addusage_7d *float64
window_5h_start *time.Time
window_1d_start *time.Time
window_7d_start *time.Time
clearedFields map[string]struct{} clearedFields map[string]struct{}
user *int64 user *int64
cleareduser bool cleareduser bool
@@ -856,6 +871,489 @@ func (m *APIKeyMutation) ResetExpiresAt() {
delete(m.clearedFields, apikey.FieldExpiresAt) delete(m.clearedFields, apikey.FieldExpiresAt)
} }
// SetRateLimit5h sets the "rate_limit_5h" field.
func (m *APIKeyMutation) SetRateLimit5h(f float64) {
m.rate_limit_5h = &f
m.addrate_limit_5h = nil
}
// RateLimit5h returns the value of the "rate_limit_5h" field in the mutation.
func (m *APIKeyMutation) RateLimit5h() (r float64, exists bool) {
v := m.rate_limit_5h
if v == nil {
return
}
return *v, true
}
// OldRateLimit5h returns the old "rate_limit_5h" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldRateLimit5h(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldRateLimit5h is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldRateLimit5h requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldRateLimit5h: %w", err)
}
return oldValue.RateLimit5h, nil
}
// AddRateLimit5h adds f to the "rate_limit_5h" field.
func (m *APIKeyMutation) AddRateLimit5h(f float64) {
if m.addrate_limit_5h != nil {
*m.addrate_limit_5h += f
} else {
m.addrate_limit_5h = &f
}
}
// AddedRateLimit5h returns the value that was added to the "rate_limit_5h" field in this mutation.
func (m *APIKeyMutation) AddedRateLimit5h() (r float64, exists bool) {
v := m.addrate_limit_5h
if v == nil {
return
}
return *v, true
}
// ResetRateLimit5h resets all changes to the "rate_limit_5h" field.
func (m *APIKeyMutation) ResetRateLimit5h() {
m.rate_limit_5h = nil
m.addrate_limit_5h = nil
}
// SetRateLimit1d sets the "rate_limit_1d" field.
func (m *APIKeyMutation) SetRateLimit1d(f float64) {
m.rate_limit_1d = &f
m.addrate_limit_1d = nil
}
// RateLimit1d returns the value of the "rate_limit_1d" field in the mutation.
func (m *APIKeyMutation) RateLimit1d() (r float64, exists bool) {
v := m.rate_limit_1d
if v == nil {
return
}
return *v, true
}
// OldRateLimit1d returns the old "rate_limit_1d" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldRateLimit1d(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldRateLimit1d is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldRateLimit1d requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldRateLimit1d: %w", err)
}
return oldValue.RateLimit1d, nil
}
// AddRateLimit1d adds f to the "rate_limit_1d" field.
func (m *APIKeyMutation) AddRateLimit1d(f float64) {
if m.addrate_limit_1d != nil {
*m.addrate_limit_1d += f
} else {
m.addrate_limit_1d = &f
}
}
// AddedRateLimit1d returns the value that was added to the "rate_limit_1d" field in this mutation.
func (m *APIKeyMutation) AddedRateLimit1d() (r float64, exists bool) {
v := m.addrate_limit_1d
if v == nil {
return
}
return *v, true
}
// ResetRateLimit1d resets all changes to the "rate_limit_1d" field.
func (m *APIKeyMutation) ResetRateLimit1d() {
m.rate_limit_1d = nil
m.addrate_limit_1d = nil
}
// SetRateLimit7d sets the "rate_limit_7d" field.
func (m *APIKeyMutation) SetRateLimit7d(f float64) {
m.rate_limit_7d = &f
m.addrate_limit_7d = nil
}
// RateLimit7d returns the value of the "rate_limit_7d" field in the mutation.
func (m *APIKeyMutation) RateLimit7d() (r float64, exists bool) {
v := m.rate_limit_7d
if v == nil {
return
}
return *v, true
}
// OldRateLimit7d returns the old "rate_limit_7d" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldRateLimit7d(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldRateLimit7d is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldRateLimit7d requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldRateLimit7d: %w", err)
}
return oldValue.RateLimit7d, nil
}
// AddRateLimit7d adds f to the "rate_limit_7d" field.
func (m *APIKeyMutation) AddRateLimit7d(f float64) {
if m.addrate_limit_7d != nil {
*m.addrate_limit_7d += f
} else {
m.addrate_limit_7d = &f
}
}
// AddedRateLimit7d returns the value that was added to the "rate_limit_7d" field in this mutation.
func (m *APIKeyMutation) AddedRateLimit7d() (r float64, exists bool) {
v := m.addrate_limit_7d
if v == nil {
return
}
return *v, true
}
// ResetRateLimit7d resets all changes to the "rate_limit_7d" field.
func (m *APIKeyMutation) ResetRateLimit7d() {
m.rate_limit_7d = nil
m.addrate_limit_7d = nil
}
// SetUsage5h sets the "usage_5h" field.
func (m *APIKeyMutation) SetUsage5h(f float64) {
m.usage_5h = &f
m.addusage_5h = nil
}
// Usage5h returns the value of the "usage_5h" field in the mutation.
func (m *APIKeyMutation) Usage5h() (r float64, exists bool) {
v := m.usage_5h
if v == nil {
return
}
return *v, true
}
// OldUsage5h returns the old "usage_5h" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldUsage5h(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUsage5h is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUsage5h requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUsage5h: %w", err)
}
return oldValue.Usage5h, nil
}
// AddUsage5h adds f to the "usage_5h" field.
func (m *APIKeyMutation) AddUsage5h(f float64) {
if m.addusage_5h != nil {
*m.addusage_5h += f
} else {
m.addusage_5h = &f
}
}
// AddedUsage5h returns the value that was added to the "usage_5h" field in this mutation.
func (m *APIKeyMutation) AddedUsage5h() (r float64, exists bool) {
v := m.addusage_5h
if v == nil {
return
}
return *v, true
}
// ResetUsage5h resets all changes to the "usage_5h" field.
func (m *APIKeyMutation) ResetUsage5h() {
m.usage_5h = nil
m.addusage_5h = nil
}
// SetUsage1d sets the "usage_1d" field.
func (m *APIKeyMutation) SetUsage1d(f float64) {
m.usage_1d = &f
m.addusage_1d = nil
}
// Usage1d returns the value of the "usage_1d" field in the mutation.
func (m *APIKeyMutation) Usage1d() (r float64, exists bool) {
v := m.usage_1d
if v == nil {
return
}
return *v, true
}
// OldUsage1d returns the old "usage_1d" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldUsage1d(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUsage1d is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUsage1d requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUsage1d: %w", err)
}
return oldValue.Usage1d, nil
}
// AddUsage1d adds f to the "usage_1d" field.
func (m *APIKeyMutation) AddUsage1d(f float64) {
if m.addusage_1d != nil {
*m.addusage_1d += f
} else {
m.addusage_1d = &f
}
}
// AddedUsage1d returns the value that was added to the "usage_1d" field in this mutation.
func (m *APIKeyMutation) AddedUsage1d() (r float64, exists bool) {
v := m.addusage_1d
if v == nil {
return
}
return *v, true
}
// ResetUsage1d resets all changes to the "usage_1d" field.
func (m *APIKeyMutation) ResetUsage1d() {
m.usage_1d = nil
m.addusage_1d = nil
}
// SetUsage7d sets the "usage_7d" field.
func (m *APIKeyMutation) SetUsage7d(f float64) {
m.usage_7d = &f
m.addusage_7d = nil
}
// Usage7d returns the value of the "usage_7d" field in the mutation.
func (m *APIKeyMutation) Usage7d() (r float64, exists bool) {
v := m.usage_7d
if v == nil {
return
}
return *v, true
}
// OldUsage7d returns the old "usage_7d" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldUsage7d(ctx context.Context) (v float64, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldUsage7d is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldUsage7d requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldUsage7d: %w", err)
}
return oldValue.Usage7d, nil
}
// AddUsage7d adds f to the "usage_7d" field.
func (m *APIKeyMutation) AddUsage7d(f float64) {
if m.addusage_7d != nil {
*m.addusage_7d += f
} else {
m.addusage_7d = &f
}
}
// AddedUsage7d returns the value that was added to the "usage_7d" field in this mutation.
func (m *APIKeyMutation) AddedUsage7d() (r float64, exists bool) {
v := m.addusage_7d
if v == nil {
return
}
return *v, true
}
// ResetUsage7d resets all changes to the "usage_7d" field.
func (m *APIKeyMutation) ResetUsage7d() {
m.usage_7d = nil
m.addusage_7d = nil
}
// SetWindow5hStart sets the "window_5h_start" field.
func (m *APIKeyMutation) SetWindow5hStart(t time.Time) {
m.window_5h_start = &t
}
// Window5hStart returns the value of the "window_5h_start" field in the mutation.
func (m *APIKeyMutation) Window5hStart() (r time.Time, exists bool) {
v := m.window_5h_start
if v == nil {
return
}
return *v, true
}
// OldWindow5hStart returns the old "window_5h_start" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldWindow5hStart(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldWindow5hStart is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldWindow5hStart requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldWindow5hStart: %w", err)
}
return oldValue.Window5hStart, nil
}
// ClearWindow5hStart clears the value of the "window_5h_start" field.
func (m *APIKeyMutation) ClearWindow5hStart() {
m.window_5h_start = nil
m.clearedFields[apikey.FieldWindow5hStart] = struct{}{}
}
// Window5hStartCleared returns if the "window_5h_start" field was cleared in this mutation.
func (m *APIKeyMutation) Window5hStartCleared() bool {
_, ok := m.clearedFields[apikey.FieldWindow5hStart]
return ok
}
// ResetWindow5hStart resets all changes to the "window_5h_start" field.
func (m *APIKeyMutation) ResetWindow5hStart() {
m.window_5h_start = nil
delete(m.clearedFields, apikey.FieldWindow5hStart)
}
// SetWindow1dStart sets the "window_1d_start" field.
func (m *APIKeyMutation) SetWindow1dStart(t time.Time) {
m.window_1d_start = &t
}
// Window1dStart returns the value of the "window_1d_start" field in the mutation.
func (m *APIKeyMutation) Window1dStart() (r time.Time, exists bool) {
v := m.window_1d_start
if v == nil {
return
}
return *v, true
}
// OldWindow1dStart returns the old "window_1d_start" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldWindow1dStart(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldWindow1dStart is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldWindow1dStart requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldWindow1dStart: %w", err)
}
return oldValue.Window1dStart, nil
}
// ClearWindow1dStart clears the value of the "window_1d_start" field.
func (m *APIKeyMutation) ClearWindow1dStart() {
m.window_1d_start = nil
m.clearedFields[apikey.FieldWindow1dStart] = struct{}{}
}
// Window1dStartCleared returns if the "window_1d_start" field was cleared in this mutation.
func (m *APIKeyMutation) Window1dStartCleared() bool {
_, ok := m.clearedFields[apikey.FieldWindow1dStart]
return ok
}
// ResetWindow1dStart resets all changes to the "window_1d_start" field.
func (m *APIKeyMutation) ResetWindow1dStart() {
m.window_1d_start = nil
delete(m.clearedFields, apikey.FieldWindow1dStart)
}
// SetWindow7dStart sets the "window_7d_start" field.
func (m *APIKeyMutation) SetWindow7dStart(t time.Time) {
m.window_7d_start = &t
}
// Window7dStart returns the value of the "window_7d_start" field in the mutation.
func (m *APIKeyMutation) Window7dStart() (r time.Time, exists bool) {
v := m.window_7d_start
if v == nil {
return
}
return *v, true
}
// OldWindow7dStart returns the old "window_7d_start" field's value of the APIKey entity.
// If the APIKey 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 *APIKeyMutation) OldWindow7dStart(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldWindow7dStart is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldWindow7dStart requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldWindow7dStart: %w", err)
}
return oldValue.Window7dStart, nil
}
// ClearWindow7dStart clears the value of the "window_7d_start" field.
func (m *APIKeyMutation) ClearWindow7dStart() {
m.window_7d_start = nil
m.clearedFields[apikey.FieldWindow7dStart] = struct{}{}
}
// Window7dStartCleared returns if the "window_7d_start" field was cleared in this mutation.
func (m *APIKeyMutation) Window7dStartCleared() bool {
_, ok := m.clearedFields[apikey.FieldWindow7dStart]
return ok
}
// ResetWindow7dStart resets all changes to the "window_7d_start" field.
func (m *APIKeyMutation) ResetWindow7dStart() {
m.window_7d_start = nil
delete(m.clearedFields, apikey.FieldWindow7dStart)
}
// ClearUser clears the "user" edge to the User entity. // ClearUser clears the "user" edge to the User entity.
func (m *APIKeyMutation) ClearUser() { func (m *APIKeyMutation) ClearUser() {
m.cleareduser = true m.cleareduser = true
@@ -998,7 +1496,7 @@ func (m *APIKeyMutation) Type() string {
// order to get all numeric fields that were incremented/decremented, call // order to get all numeric fields that were incremented/decremented, call
// AddedFields(). // AddedFields().
func (m *APIKeyMutation) Fields() []string { func (m *APIKeyMutation) Fields() []string {
fields := make([]string, 0, 14) fields := make([]string, 0, 23)
if m.created_at != nil { if m.created_at != nil {
fields = append(fields, apikey.FieldCreatedAt) fields = append(fields, apikey.FieldCreatedAt)
} }
@@ -1041,6 +1539,33 @@ func (m *APIKeyMutation) Fields() []string {
if m.expires_at != nil { if m.expires_at != nil {
fields = append(fields, apikey.FieldExpiresAt) fields = append(fields, apikey.FieldExpiresAt)
} }
if m.rate_limit_5h != nil {
fields = append(fields, apikey.FieldRateLimit5h)
}
if m.rate_limit_1d != nil {
fields = append(fields, apikey.FieldRateLimit1d)
}
if m.rate_limit_7d != nil {
fields = append(fields, apikey.FieldRateLimit7d)
}
if m.usage_5h != nil {
fields = append(fields, apikey.FieldUsage5h)
}
if m.usage_1d != nil {
fields = append(fields, apikey.FieldUsage1d)
}
if m.usage_7d != nil {
fields = append(fields, apikey.FieldUsage7d)
}
if m.window_5h_start != nil {
fields = append(fields, apikey.FieldWindow5hStart)
}
if m.window_1d_start != nil {
fields = append(fields, apikey.FieldWindow1dStart)
}
if m.window_7d_start != nil {
fields = append(fields, apikey.FieldWindow7dStart)
}
return fields return fields
} }
@@ -1077,6 +1602,24 @@ func (m *APIKeyMutation) Field(name string) (ent.Value, bool) {
return m.QuotaUsed() return m.QuotaUsed()
case apikey.FieldExpiresAt: case apikey.FieldExpiresAt:
return m.ExpiresAt() return m.ExpiresAt()
case apikey.FieldRateLimit5h:
return m.RateLimit5h()
case apikey.FieldRateLimit1d:
return m.RateLimit1d()
case apikey.FieldRateLimit7d:
return m.RateLimit7d()
case apikey.FieldUsage5h:
return m.Usage5h()
case apikey.FieldUsage1d:
return m.Usage1d()
case apikey.FieldUsage7d:
return m.Usage7d()
case apikey.FieldWindow5hStart:
return m.Window5hStart()
case apikey.FieldWindow1dStart:
return m.Window1dStart()
case apikey.FieldWindow7dStart:
return m.Window7dStart()
} }
return nil, false return nil, false
} }
@@ -1114,6 +1657,24 @@ func (m *APIKeyMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldQuotaUsed(ctx) return m.OldQuotaUsed(ctx)
case apikey.FieldExpiresAt: case apikey.FieldExpiresAt:
return m.OldExpiresAt(ctx) return m.OldExpiresAt(ctx)
case apikey.FieldRateLimit5h:
return m.OldRateLimit5h(ctx)
case apikey.FieldRateLimit1d:
return m.OldRateLimit1d(ctx)
case apikey.FieldRateLimit7d:
return m.OldRateLimit7d(ctx)
case apikey.FieldUsage5h:
return m.OldUsage5h(ctx)
case apikey.FieldUsage1d:
return m.OldUsage1d(ctx)
case apikey.FieldUsage7d:
return m.OldUsage7d(ctx)
case apikey.FieldWindow5hStart:
return m.OldWindow5hStart(ctx)
case apikey.FieldWindow1dStart:
return m.OldWindow1dStart(ctx)
case apikey.FieldWindow7dStart:
return m.OldWindow7dStart(ctx)
} }
return nil, fmt.Errorf("unknown APIKey field %s", name) return nil, fmt.Errorf("unknown APIKey field %s", name)
} }
@@ -1221,6 +1782,69 @@ func (m *APIKeyMutation) SetField(name string, value ent.Value) error {
} }
m.SetExpiresAt(v) m.SetExpiresAt(v)
return nil return nil
case apikey.FieldRateLimit5h:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetRateLimit5h(v)
return nil
case apikey.FieldRateLimit1d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetRateLimit1d(v)
return nil
case apikey.FieldRateLimit7d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetRateLimit7d(v)
return nil
case apikey.FieldUsage5h:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUsage5h(v)
return nil
case apikey.FieldUsage1d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUsage1d(v)
return nil
case apikey.FieldUsage7d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetUsage7d(v)
return nil
case apikey.FieldWindow5hStart:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetWindow5hStart(v)
return nil
case apikey.FieldWindow1dStart:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetWindow1dStart(v)
return nil
case apikey.FieldWindow7dStart:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetWindow7dStart(v)
return nil
} }
return fmt.Errorf("unknown APIKey field %s", name) return fmt.Errorf("unknown APIKey field %s", name)
} }
@@ -1235,6 +1859,24 @@ func (m *APIKeyMutation) AddedFields() []string {
if m.addquota_used != nil { if m.addquota_used != nil {
fields = append(fields, apikey.FieldQuotaUsed) fields = append(fields, apikey.FieldQuotaUsed)
} }
if m.addrate_limit_5h != nil {
fields = append(fields, apikey.FieldRateLimit5h)
}
if m.addrate_limit_1d != nil {
fields = append(fields, apikey.FieldRateLimit1d)
}
if m.addrate_limit_7d != nil {
fields = append(fields, apikey.FieldRateLimit7d)
}
if m.addusage_5h != nil {
fields = append(fields, apikey.FieldUsage5h)
}
if m.addusage_1d != nil {
fields = append(fields, apikey.FieldUsage1d)
}
if m.addusage_7d != nil {
fields = append(fields, apikey.FieldUsage7d)
}
return fields return fields
} }
@@ -1247,6 +1889,18 @@ func (m *APIKeyMutation) AddedField(name string) (ent.Value, bool) {
return m.AddedQuota() return m.AddedQuota()
case apikey.FieldQuotaUsed: case apikey.FieldQuotaUsed:
return m.AddedQuotaUsed() return m.AddedQuotaUsed()
case apikey.FieldRateLimit5h:
return m.AddedRateLimit5h()
case apikey.FieldRateLimit1d:
return m.AddedRateLimit1d()
case apikey.FieldRateLimit7d:
return m.AddedRateLimit7d()
case apikey.FieldUsage5h:
return m.AddedUsage5h()
case apikey.FieldUsage1d:
return m.AddedUsage1d()
case apikey.FieldUsage7d:
return m.AddedUsage7d()
} }
return nil, false return nil, false
} }
@@ -1270,6 +1924,48 @@ func (m *APIKeyMutation) AddField(name string, value ent.Value) error {
} }
m.AddQuotaUsed(v) m.AddQuotaUsed(v)
return nil return nil
case apikey.FieldRateLimit5h:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddRateLimit5h(v)
return nil
case apikey.FieldRateLimit1d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddRateLimit1d(v)
return nil
case apikey.FieldRateLimit7d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddRateLimit7d(v)
return nil
case apikey.FieldUsage5h:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddUsage5h(v)
return nil
case apikey.FieldUsage1d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddUsage1d(v)
return nil
case apikey.FieldUsage7d:
v, ok := value.(float64)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.AddUsage7d(v)
return nil
} }
return fmt.Errorf("unknown APIKey numeric field %s", name) return fmt.Errorf("unknown APIKey numeric field %s", name)
} }
@@ -1296,6 +1992,15 @@ func (m *APIKeyMutation) ClearedFields() []string {
if m.FieldCleared(apikey.FieldExpiresAt) { if m.FieldCleared(apikey.FieldExpiresAt) {
fields = append(fields, apikey.FieldExpiresAt) fields = append(fields, apikey.FieldExpiresAt)
} }
if m.FieldCleared(apikey.FieldWindow5hStart) {
fields = append(fields, apikey.FieldWindow5hStart)
}
if m.FieldCleared(apikey.FieldWindow1dStart) {
fields = append(fields, apikey.FieldWindow1dStart)
}
if m.FieldCleared(apikey.FieldWindow7dStart) {
fields = append(fields, apikey.FieldWindow7dStart)
}
return fields return fields
} }
@@ -1328,6 +2033,15 @@ func (m *APIKeyMutation) ClearField(name string) error {
case apikey.FieldExpiresAt: case apikey.FieldExpiresAt:
m.ClearExpiresAt() m.ClearExpiresAt()
return nil return nil
case apikey.FieldWindow5hStart:
m.ClearWindow5hStart()
return nil
case apikey.FieldWindow1dStart:
m.ClearWindow1dStart()
return nil
case apikey.FieldWindow7dStart:
m.ClearWindow7dStart()
return nil
} }
return fmt.Errorf("unknown APIKey nullable field %s", name) return fmt.Errorf("unknown APIKey nullable field %s", name)
} }
@@ -1378,6 +2092,33 @@ func (m *APIKeyMutation) ResetField(name string) error {
case apikey.FieldExpiresAt: case apikey.FieldExpiresAt:
m.ResetExpiresAt() m.ResetExpiresAt()
return nil return nil
case apikey.FieldRateLimit5h:
m.ResetRateLimit5h()
return nil
case apikey.FieldRateLimit1d:
m.ResetRateLimit1d()
return nil
case apikey.FieldRateLimit7d:
m.ResetRateLimit7d()
return nil
case apikey.FieldUsage5h:
m.ResetUsage5h()
return nil
case apikey.FieldUsage1d:
m.ResetUsage1d()
return nil
case apikey.FieldUsage7d:
m.ResetUsage7d()
return nil
case apikey.FieldWindow5hStart:
m.ResetWindow5hStart()
return nil
case apikey.FieldWindow1dStart:
m.ResetWindow1dStart()
return nil
case apikey.FieldWindow7dStart:
m.ResetWindow7dStart()
return nil
} }
return fmt.Errorf("unknown APIKey field %s", name) return fmt.Errorf("unknown APIKey field %s", name)
} }

View File

@@ -102,6 +102,30 @@ func init() {
apikeyDescQuotaUsed := apikeyFields[9].Descriptor() apikeyDescQuotaUsed := apikeyFields[9].Descriptor()
// apikey.DefaultQuotaUsed holds the default value on creation for the quota_used field. // apikey.DefaultQuotaUsed holds the default value on creation for the quota_used field.
apikey.DefaultQuotaUsed = apikeyDescQuotaUsed.Default.(float64) apikey.DefaultQuotaUsed = apikeyDescQuotaUsed.Default.(float64)
// apikeyDescRateLimit5h is the schema descriptor for rate_limit_5h field.
apikeyDescRateLimit5h := apikeyFields[11].Descriptor()
// apikey.DefaultRateLimit5h holds the default value on creation for the rate_limit_5h field.
apikey.DefaultRateLimit5h = apikeyDescRateLimit5h.Default.(float64)
// apikeyDescRateLimit1d is the schema descriptor for rate_limit_1d field.
apikeyDescRateLimit1d := apikeyFields[12].Descriptor()
// apikey.DefaultRateLimit1d holds the default value on creation for the rate_limit_1d field.
apikey.DefaultRateLimit1d = apikeyDescRateLimit1d.Default.(float64)
// apikeyDescRateLimit7d is the schema descriptor for rate_limit_7d field.
apikeyDescRateLimit7d := apikeyFields[13].Descriptor()
// apikey.DefaultRateLimit7d holds the default value on creation for the rate_limit_7d field.
apikey.DefaultRateLimit7d = apikeyDescRateLimit7d.Default.(float64)
// apikeyDescUsage5h is the schema descriptor for usage_5h field.
apikeyDescUsage5h := apikeyFields[14].Descriptor()
// apikey.DefaultUsage5h holds the default value on creation for the usage_5h field.
apikey.DefaultUsage5h = apikeyDescUsage5h.Default.(float64)
// apikeyDescUsage1d is the schema descriptor for usage_1d field.
apikeyDescUsage1d := apikeyFields[15].Descriptor()
// apikey.DefaultUsage1d holds the default value on creation for the usage_1d field.
apikey.DefaultUsage1d = apikeyDescUsage1d.Default.(float64)
// apikeyDescUsage7d is the schema descriptor for usage_7d field.
apikeyDescUsage7d := apikeyFields[16].Descriptor()
// apikey.DefaultUsage7d holds the default value on creation for the usage_7d field.
apikey.DefaultUsage7d = apikeyDescUsage7d.Default.(float64)
accountMixin := schema.Account{}.Mixin() accountMixin := schema.Account{}.Mixin()
accountMixinHooks1 := accountMixin[1].Hooks() accountMixinHooks1 := accountMixin[1].Hooks()
account.Hooks[0] = accountMixinHooks1[0] account.Hooks[0] = accountMixinHooks1[0]

View File

@@ -74,6 +74,47 @@ func (APIKey) Fields() []ent.Field {
Optional(). Optional().
Nillable(). Nillable().
Comment("Expiration time for this API key (null = never expires)"), Comment("Expiration time for this API key (null = never expires)"),
// ========== Rate limit fields ==========
// Rate limit configuration (0 = unlimited)
field.Float("rate_limit_5h").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per 5 hours (0 = unlimited)"),
field.Float("rate_limit_1d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per day (0 = unlimited)"),
field.Float("rate_limit_7d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Rate limit in USD per 7 days (0 = unlimited)"),
// Rate limit usage tracking
field.Float("usage_5h").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 5h window"),
field.Float("usage_1d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 1d window"),
field.Float("usage_7d").
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}).
Default(0).
Comment("Used amount in USD for the current 7d window"),
// Window start times
field.Time("window_5h_start").
Optional().
Nillable().
Comment("Start time of the current 5h rate limit window"),
field.Time("window_1d_start").
Optional().
Nillable().
Comment("Start time of the current 1d rate limit window"),
field.Time("window_7d_start").
Optional().
Nillable().
Comment("Start time of the current 7d rate limit window"),
} }
} }

View File

@@ -180,8 +180,6 @@ require (
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
golang.org/x/tools v0.41.0 // indirect golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
modernc.org/libc v1.67.6 // indirect modernc.org/libc v1.67.6 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect

View File

@@ -36,6 +36,11 @@ type CreateAPIKeyRequest struct {
IPBlacklist []string `json:"ip_blacklist"` // IP 黑名单 IPBlacklist []string `json:"ip_blacklist"` // IP 黑名单
Quota *float64 `json:"quota"` // 配额限制 (USD) Quota *float64 `json:"quota"` // 配额限制 (USD)
ExpiresInDays *int `json:"expires_in_days"` // 过期天数 ExpiresInDays *int `json:"expires_in_days"` // 过期天数
// Rate limit fields (0 = unlimited)
RateLimit5h *float64 `json:"rate_limit_5h"`
RateLimit1d *float64 `json:"rate_limit_1d"`
RateLimit7d *float64 `json:"rate_limit_7d"`
} }
// UpdateAPIKeyRequest represents the update API key request payload // UpdateAPIKeyRequest represents the update API key request payload
@@ -48,6 +53,12 @@ type UpdateAPIKeyRequest struct {
Quota *float64 `json:"quota"` // 配额限制 (USD), 0=无限制 Quota *float64 `json:"quota"` // 配额限制 (USD), 0=无限制
ExpiresAt *string `json:"expires_at"` // 过期时间 (ISO 8601) ExpiresAt *string `json:"expires_at"` // 过期时间 (ISO 8601)
ResetQuota *bool `json:"reset_quota"` // 重置已用配额 ResetQuota *bool `json:"reset_quota"` // 重置已用配额
// Rate limit fields (nil = no change, 0 = unlimited)
RateLimit5h *float64 `json:"rate_limit_5h"`
RateLimit1d *float64 `json:"rate_limit_1d"`
RateLimit7d *float64 `json:"rate_limit_7d"`
ResetRateLimitUsage *bool `json:"reset_rate_limit_usage"` // 重置限速用量
} }
// List handles listing user's API keys with pagination // List handles listing user's API keys with pagination
@@ -131,6 +142,15 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
if req.Quota != nil { if req.Quota != nil {
svcReq.Quota = *req.Quota svcReq.Quota = *req.Quota
} }
if req.RateLimit5h != nil {
svcReq.RateLimit5h = *req.RateLimit5h
}
if req.RateLimit1d != nil {
svcReq.RateLimit1d = *req.RateLimit1d
}
if req.RateLimit7d != nil {
svcReq.RateLimit7d = *req.RateLimit7d
}
executeUserIdempotentJSON(c, "user.api_keys.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) { executeUserIdempotentJSON(c, "user.api_keys.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
key, err := h.apiKeyService.Create(ctx, subject.UserID, svcReq) key, err := h.apiKeyService.Create(ctx, subject.UserID, svcReq)
@@ -163,10 +183,14 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
} }
svcReq := service.UpdateAPIKeyRequest{ svcReq := service.UpdateAPIKeyRequest{
IPWhitelist: req.IPWhitelist, IPWhitelist: req.IPWhitelist,
IPBlacklist: req.IPBlacklist, IPBlacklist: req.IPBlacklist,
Quota: req.Quota, Quota: req.Quota,
ResetQuota: req.ResetQuota, ResetQuota: req.ResetQuota,
RateLimit5h: req.RateLimit5h,
RateLimit1d: req.RateLimit1d,
RateLimit7d: req.RateLimit7d,
ResetRateLimitUsage: req.ResetRateLimitUsage,
} }
if req.Name != "" { if req.Name != "" {
svcReq.Name = &req.Name svcReq.Name = &req.Name

View File

@@ -72,22 +72,31 @@ func APIKeyFromService(k *service.APIKey) *APIKey {
return nil return nil
} }
return &APIKey{ return &APIKey{
ID: k.ID, ID: k.ID,
UserID: k.UserID, UserID: k.UserID,
Key: k.Key, Key: k.Key,
Name: k.Name, Name: k.Name,
GroupID: k.GroupID, GroupID: k.GroupID,
Status: k.Status, Status: k.Status,
IPWhitelist: k.IPWhitelist, IPWhitelist: k.IPWhitelist,
IPBlacklist: k.IPBlacklist, IPBlacklist: k.IPBlacklist,
LastUsedAt: k.LastUsedAt, LastUsedAt: k.LastUsedAt,
Quota: k.Quota, Quota: k.Quota,
QuotaUsed: k.QuotaUsed, QuotaUsed: k.QuotaUsed,
ExpiresAt: k.ExpiresAt, ExpiresAt: k.ExpiresAt,
CreatedAt: k.CreatedAt, CreatedAt: k.CreatedAt,
UpdatedAt: k.UpdatedAt, UpdatedAt: k.UpdatedAt,
User: UserFromServiceShallow(k.User), RateLimit5h: k.RateLimit5h,
Group: GroupFromServiceShallow(k.Group), RateLimit1d: k.RateLimit1d,
RateLimit7d: k.RateLimit7d,
Usage5h: k.Usage5h,
Usage1d: k.Usage1d,
Usage7d: k.Usage7d,
Window5hStart: k.Window5hStart,
Window1dStart: k.Window1dStart,
Window7dStart: k.Window7dStart,
User: UserFromServiceShallow(k.User),
Group: GroupFromServiceShallow(k.Group),
} }
} }

View File

@@ -47,6 +47,17 @@ type APIKey struct {
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
// Rate limit fields
RateLimit5h float64 `json:"rate_limit_5h"`
RateLimit1d float64 `json:"rate_limit_1d"`
RateLimit7d float64 `json:"rate_limit_7d"`
Usage5h float64 `json:"usage_5h"`
Usage1d float64 `json:"usage_1d"`
Usage7d float64 `json:"usage_7d"`
Window5hStart *time.Time `json:"window_5h_start"`
Window1dStart *time.Time `json:"window_1d_start"`
Window7dStart *time.Time `json:"window_7d_start"`
User *User `json:"user,omitempty"` User *User `json:"user,omitempty"`
Group *Group `json:"group,omitempty"` Group *Group `json:"group,omitempty"`
} }

View File

@@ -1445,6 +1445,18 @@ func billingErrorDetails(err error) (status int, code, message string) {
} }
return http.StatusServiceUnavailable, "billing_service_error", msg return http.StatusServiceUnavailable, "billing_service_error", msg
} }
if errors.Is(err, service.ErrAPIKeyRateLimit5hExceeded) {
msg := pkgerrors.Message(err)
return http.StatusTooManyRequests, "rate_limit_exceeded", msg
}
if errors.Is(err, service.ErrAPIKeyRateLimit1dExceeded) {
msg := pkgerrors.Message(err)
return http.StatusTooManyRequests, "rate_limit_exceeded", msg
}
if errors.Is(err, service.ErrAPIKeyRateLimit7dExceeded) {
msg := pkgerrors.Message(err)
return http.StatusTooManyRequests, "rate_limit_exceeded", msg
}
msg := pkgerrors.Message(err) msg := pkgerrors.Message(err)
if msg == "" { if msg == "" {
logger.L().With( logger.L().With(

View File

@@ -2,6 +2,7 @@ package repository
import ( import (
"context" "context"
"database/sql"
"time" "time"
dbent "github.com/Wei-Shaw/sub2api/ent" dbent "github.com/Wei-Shaw/sub2api/ent"
@@ -16,10 +17,11 @@ import (
type apiKeyRepository struct { type apiKeyRepository struct {
client *dbent.Client client *dbent.Client
sql sqlExecutor
} }
func NewAPIKeyRepository(client *dbent.Client) service.APIKeyRepository { func NewAPIKeyRepository(client *dbent.Client, sqlDB *sql.DB) service.APIKeyRepository {
return &apiKeyRepository{client: client} return &apiKeyRepository{client: client, sql: sqlDB}
} }
func (r *apiKeyRepository) activeQuery() *dbent.APIKeyQuery { func (r *apiKeyRepository) activeQuery() *dbent.APIKeyQuery {
@@ -37,7 +39,10 @@ func (r *apiKeyRepository) Create(ctx context.Context, key *service.APIKey) erro
SetNillableLastUsedAt(key.LastUsedAt). SetNillableLastUsedAt(key.LastUsedAt).
SetQuota(key.Quota). SetQuota(key.Quota).
SetQuotaUsed(key.QuotaUsed). SetQuotaUsed(key.QuotaUsed).
SetNillableExpiresAt(key.ExpiresAt) SetNillableExpiresAt(key.ExpiresAt).
SetRateLimit5h(key.RateLimit5h).
SetRateLimit1d(key.RateLimit1d).
SetRateLimit7d(key.RateLimit7d)
if len(key.IPWhitelist) > 0 { if len(key.IPWhitelist) > 0 {
builder.SetIPWhitelist(key.IPWhitelist) builder.SetIPWhitelist(key.IPWhitelist)
@@ -118,6 +123,9 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
apikey.FieldQuota, apikey.FieldQuota,
apikey.FieldQuotaUsed, apikey.FieldQuotaUsed,
apikey.FieldExpiresAt, apikey.FieldExpiresAt,
apikey.FieldRateLimit5h,
apikey.FieldRateLimit1d,
apikey.FieldRateLimit7d,
). ).
WithUser(func(q *dbent.UserQuery) { WithUser(func(q *dbent.UserQuery) {
q.Select( q.Select(
@@ -179,6 +187,12 @@ func (r *apiKeyRepository) Update(ctx context.Context, key *service.APIKey) erro
SetStatus(key.Status). SetStatus(key.Status).
SetQuota(key.Quota). SetQuota(key.Quota).
SetQuotaUsed(key.QuotaUsed). SetQuotaUsed(key.QuotaUsed).
SetRateLimit5h(key.RateLimit5h).
SetRateLimit1d(key.RateLimit1d).
SetRateLimit7d(key.RateLimit7d).
SetUsage5h(key.Usage5h).
SetUsage1d(key.Usage1d).
SetUsage7d(key.Usage7d).
SetUpdatedAt(now) SetUpdatedAt(now)
if key.GroupID != nil { if key.GroupID != nil {
builder.SetGroupID(*key.GroupID) builder.SetGroupID(*key.GroupID)
@@ -193,6 +207,23 @@ func (r *apiKeyRepository) Update(ctx context.Context, key *service.APIKey) erro
builder.ClearExpiresAt() builder.ClearExpiresAt()
} }
// Rate limit window start times
if key.Window5hStart != nil {
builder.SetWindow5hStart(*key.Window5hStart)
} else {
builder.ClearWindow5hStart()
}
if key.Window1dStart != nil {
builder.SetWindow1dStart(*key.Window1dStart)
} else {
builder.ClearWindow1dStart()
}
if key.Window7dStart != nil {
builder.SetWindow7dStart(*key.Window7dStart)
} else {
builder.ClearWindow7dStart()
}
// IP 限制字段 // IP 限制字段
if len(key.IPWhitelist) > 0 { if len(key.IPWhitelist) > 0 {
builder.SetIPWhitelist(key.IPWhitelist) builder.SetIPWhitelist(key.IPWhitelist)
@@ -412,25 +443,88 @@ func (r *apiKeyRepository) UpdateLastUsed(ctx context.Context, id int64, usedAt
return nil return nil
} }
// IncrementRateLimitUsage atomically increments all rate limit usage counters and initializes
// window start times via COALESCE if not already set.
func (r *apiKeyRepository) IncrementRateLimitUsage(ctx context.Context, id int64, cost float64) error {
_, err := r.sql.ExecContext(ctx, `
UPDATE api_keys SET
usage_5h = usage_5h + $1,
usage_1d = usage_1d + $1,
usage_7d = usage_7d + $1,
window_5h_start = COALESCE(window_5h_start, NOW()),
window_1d_start = COALESCE(window_1d_start, NOW()),
window_7d_start = COALESCE(window_7d_start, NOW()),
updated_at = NOW()
WHERE id = $2 AND deleted_at IS NULL`,
cost, id)
return err
}
// ResetRateLimitWindows resets expired rate limit windows atomically.
func (r *apiKeyRepository) ResetRateLimitWindows(ctx context.Context, id int64) error {
_, err := r.sql.ExecContext(ctx, `
UPDATE api_keys SET
usage_5h = CASE WHEN window_5h_start IS NOT NULL AND window_5h_start + INTERVAL '5 hours' <= NOW() THEN 0 ELSE usage_5h END,
window_5h_start = CASE WHEN window_5h_start IS NOT NULL AND window_5h_start + INTERVAL '5 hours' <= NOW() THEN NOW() ELSE window_5h_start END,
usage_1d = CASE WHEN window_1d_start IS NOT NULL AND window_1d_start + INTERVAL '24 hours' <= NOW() THEN 0 ELSE usage_1d END,
window_1d_start = CASE WHEN window_1d_start IS NOT NULL AND window_1d_start + INTERVAL '24 hours' <= NOW() THEN NOW() ELSE window_1d_start END,
usage_7d = CASE WHEN window_7d_start IS NOT NULL AND window_7d_start + INTERVAL '7 days' <= NOW() THEN 0 ELSE usage_7d END,
window_7d_start = CASE WHEN window_7d_start IS NOT NULL AND window_7d_start + INTERVAL '7 days' <= NOW() THEN NOW() ELSE window_7d_start END,
updated_at = NOW()
WHERE id = $1 AND deleted_at IS NULL`,
id)
return err
}
// GetRateLimitData returns the current rate limit usage and window start times for an API key.
func (r *apiKeyRepository) GetRateLimitData(ctx context.Context, id int64) (*service.APIKeyRateLimitData, error) {
rows, err := r.sql.QueryContext(ctx, `
SELECT usage_5h, usage_1d, usage_7d, window_5h_start, window_1d_start, window_7d_start
FROM api_keys
WHERE id = $1 AND deleted_at IS NULL`,
id)
if err != nil {
return nil, err
}
defer rows.Close()
if !rows.Next() {
return nil, service.ErrAPIKeyNotFound
}
data := &service.APIKeyRateLimitData{}
if err := rows.Scan(&data.Usage5h, &data.Usage1d, &data.Usage7d, &data.Window5hStart, &data.Window1dStart, &data.Window7dStart); err != nil {
return nil, err
}
return data, rows.Err()
}
func apiKeyEntityToService(m *dbent.APIKey) *service.APIKey { func apiKeyEntityToService(m *dbent.APIKey) *service.APIKey {
if m == nil { if m == nil {
return nil return nil
} }
out := &service.APIKey{ out := &service.APIKey{
ID: m.ID, ID: m.ID,
UserID: m.UserID, UserID: m.UserID,
Key: m.Key, Key: m.Key,
Name: m.Name, Name: m.Name,
Status: m.Status, Status: m.Status,
IPWhitelist: m.IPWhitelist, IPWhitelist: m.IPWhitelist,
IPBlacklist: m.IPBlacklist, IPBlacklist: m.IPBlacklist,
LastUsedAt: m.LastUsedAt, LastUsedAt: m.LastUsedAt,
CreatedAt: m.CreatedAt, CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt, UpdatedAt: m.UpdatedAt,
GroupID: m.GroupID, GroupID: m.GroupID,
Quota: m.Quota, Quota: m.Quota,
QuotaUsed: m.QuotaUsed, QuotaUsed: m.QuotaUsed,
ExpiresAt: m.ExpiresAt, ExpiresAt: m.ExpiresAt,
RateLimit5h: m.RateLimit5h,
RateLimit1d: m.RateLimit1d,
RateLimit7d: m.RateLimit7d,
Usage5h: m.Usage5h,
Usage1d: m.Usage1d,
Usage7d: m.Usage7d,
Window5hStart: m.Window5hStart,
Window1dStart: m.Window1dStart,
Window7dStart: m.Window7dStart,
} }
if m.Edges.User != nil { if m.Edges.User != nil {
out.User = userEntityToService(m.Edges.User) out.User = userEntityToService(m.Edges.User)

View File

@@ -14,10 +14,12 @@ import (
) )
const ( const (
billingBalanceKeyPrefix = "billing:balance:" billingBalanceKeyPrefix = "billing:balance:"
billingSubKeyPrefix = "billing:sub:" billingSubKeyPrefix = "billing:sub:"
billingCacheTTL = 5 * time.Minute billingRateLimitKeyPrefix = "apikey:rate:"
billingCacheJitter = 30 * time.Second billingCacheTTL = 5 * time.Minute
billingCacheJitter = 30 * time.Second
rateLimitCacheTTL = 7 * 24 * time.Hour // 7 days matches the longest window
) )
// jitteredTTL 返回带随机抖动的 TTL防止缓存雪崩 // jitteredTTL 返回带随机抖动的 TTL防止缓存雪崩
@@ -49,6 +51,20 @@ const (
subFieldVersion = "version" subFieldVersion = "version"
) )
// billingRateLimitKey generates the Redis key for API key rate limit cache.
func billingRateLimitKey(keyID int64) string {
return fmt.Sprintf("%s%d", billingRateLimitKeyPrefix, keyID)
}
const (
rateLimitFieldUsage5h = "usage_5h"
rateLimitFieldUsage1d = "usage_1d"
rateLimitFieldUsage7d = "usage_7d"
rateLimitFieldWindow5h = "window_5h"
rateLimitFieldWindow1d = "window_1d"
rateLimitFieldWindow7d = "window_7d"
)
var ( var (
deductBalanceScript = redis.NewScript(` deductBalanceScript = redis.NewScript(`
local current = redis.call('GET', KEYS[1]) local current = redis.call('GET', KEYS[1])
@@ -73,6 +89,21 @@ var (
redis.call('EXPIRE', KEYS[1], ARGV[2]) redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1 return 1
`) `)
// updateRateLimitUsageScript atomically increments all three rate limit usage counters.
// Returns 0 if the key doesn't exist (cache miss), 1 on success.
updateRateLimitUsageScript = redis.NewScript(`
local exists = redis.call('EXISTS', KEYS[1])
if exists == 0 then
return 0
end
local cost = tonumber(ARGV[1])
redis.call('HINCRBYFLOAT', KEYS[1], 'usage_5h', cost)
redis.call('HINCRBYFLOAT', KEYS[1], 'usage_1d', cost)
redis.call('HINCRBYFLOAT', KEYS[1], 'usage_7d', cost)
redis.call('EXPIRE', KEYS[1], ARGV[2])
return 1
`)
) )
type billingCache struct { type billingCache struct {
@@ -195,3 +226,69 @@ func (c *billingCache) InvalidateSubscriptionCache(ctx context.Context, userID,
key := billingSubKey(userID, groupID) key := billingSubKey(userID, groupID)
return c.rdb.Del(ctx, key).Err() return c.rdb.Del(ctx, key).Err()
} }
func (c *billingCache) GetAPIKeyRateLimit(ctx context.Context, keyID int64) (*service.APIKeyRateLimitCacheData, error) {
key := billingRateLimitKey(keyID)
result, err := c.rdb.HGetAll(ctx, key).Result()
if err != nil {
return nil, err
}
if len(result) == 0 {
return nil, redis.Nil
}
data := &service.APIKeyRateLimitCacheData{}
if v, ok := result[rateLimitFieldUsage5h]; ok {
data.Usage5h, _ = strconv.ParseFloat(v, 64)
}
if v, ok := result[rateLimitFieldUsage1d]; ok {
data.Usage1d, _ = strconv.ParseFloat(v, 64)
}
if v, ok := result[rateLimitFieldUsage7d]; ok {
data.Usage7d, _ = strconv.ParseFloat(v, 64)
}
if v, ok := result[rateLimitFieldWindow5h]; ok {
data.Window5h, _ = strconv.ParseInt(v, 10, 64)
}
if v, ok := result[rateLimitFieldWindow1d]; ok {
data.Window1d, _ = strconv.ParseInt(v, 10, 64)
}
if v, ok := result[rateLimitFieldWindow7d]; ok {
data.Window7d, _ = strconv.ParseInt(v, 10, 64)
}
return data, nil
}
func (c *billingCache) SetAPIKeyRateLimit(ctx context.Context, keyID int64, data *service.APIKeyRateLimitCacheData) error {
if data == nil {
return nil
}
key := billingRateLimitKey(keyID)
fields := map[string]any{
rateLimitFieldUsage5h: data.Usage5h,
rateLimitFieldUsage1d: data.Usage1d,
rateLimitFieldUsage7d: data.Usage7d,
rateLimitFieldWindow5h: data.Window5h,
rateLimitFieldWindow1d: data.Window1d,
rateLimitFieldWindow7d: data.Window7d,
}
pipe := c.rdb.Pipeline()
pipe.HSet(ctx, key, fields)
pipe.Expire(ctx, key, rateLimitCacheTTL)
_, err := pipe.Exec(ctx)
return err
}
func (c *billingCache) UpdateAPIKeyRateLimitUsage(ctx context.Context, keyID int64, cost float64) error {
key := billingRateLimitKey(keyID)
_, err := updateRateLimitUsageScript.Run(ctx, c.rdb, []string{key}, cost, int(rateLimitCacheTTL.Seconds())).Result()
if err != nil && !errors.Is(err, redis.Nil) {
log.Printf("Warning: update rate limit usage cache failed for api key %d: %v", keyID, err)
return err
}
return nil
}
func (c *billingCache) InvalidateAPIKeyRateLimit(ctx context.Context, keyID int64) error {
key := billingRateLimitKey(keyID)
return c.rdb.Del(ctx, key).Err()
}

View File

@@ -95,6 +95,15 @@ func (f fakeAPIKeyRepo) UpdateLastUsed(ctx context.Context, id int64, usedAt tim
} }
return nil return nil
} }
func (f fakeAPIKeyRepo) IncrementRateLimitUsage(ctx context.Context, id int64, cost float64) error {
return nil
}
func (f fakeAPIKeyRepo) ResetRateLimitWindows(ctx context.Context, id int64) error {
return nil
}
func (f fakeAPIKeyRepo) GetRateLimitData(ctx context.Context, id int64) (*service.APIKeyRateLimitData, error) {
return &service.APIKeyRateLimitData{}, nil
}
func (f fakeGoogleSubscriptionRepo) Create(ctx context.Context, sub *service.UserSubscription) error { func (f fakeGoogleSubscriptionRepo) Create(ctx context.Context, sub *service.UserSubscription) error {
return errors.New("not implemented") return errors.New("not implemented")

View File

@@ -36,12 +36,28 @@ type APIKey struct {
Quota float64 // Quota limit in USD (0 = unlimited) Quota float64 // Quota limit in USD (0 = unlimited)
QuotaUsed float64 // Used quota amount QuotaUsed float64 // Used quota amount
ExpiresAt *time.Time // Expiration time (nil = never expires) ExpiresAt *time.Time // Expiration time (nil = never expires)
// Rate limit fields
RateLimit5h float64 // Rate limit in USD per 5h (0 = unlimited)
RateLimit1d float64 // Rate limit in USD per 1d (0 = unlimited)
RateLimit7d float64 // Rate limit in USD per 7d (0 = unlimited)
Usage5h float64 // Used amount in current 5h window
Usage1d float64 // Used amount in current 1d window
Usage7d float64 // Used amount in current 7d window
Window5hStart *time.Time // Start of current 5h window
Window1dStart *time.Time // Start of current 1d window
Window7dStart *time.Time // Start of current 7d window
} }
func (k *APIKey) IsActive() bool { func (k *APIKey) IsActive() bool {
return k.Status == StatusActive return k.Status == StatusActive
} }
// HasRateLimits returns true if any rate limit window is configured
func (k *APIKey) HasRateLimits() bool {
return k.RateLimit5h > 0 || k.RateLimit1d > 0 || k.RateLimit7d > 0
}
// IsExpired checks if the API key has expired // IsExpired checks if the API key has expired
func (k *APIKey) IsExpired() bool { func (k *APIKey) IsExpired() bool {
if k.ExpiresAt == nil { if k.ExpiresAt == nil {

View File

@@ -19,6 +19,11 @@ type APIKeyAuthSnapshot struct {
// Expiration field for API Key expiration feature // Expiration field for API Key expiration feature
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Expiration time (nil = never expires) ExpiresAt *time.Time `json:"expires_at,omitempty"` // Expiration time (nil = never expires)
// Rate limit configuration (only limits, not usage - usage read from Redis at check time)
RateLimit5h float64 `json:"rate_limit_5h"`
RateLimit1d float64 `json:"rate_limit_1d"`
RateLimit7d float64 `json:"rate_limit_7d"`
} }
// APIKeyAuthUserSnapshot 用户快照 // APIKeyAuthUserSnapshot 用户快照

View File

@@ -209,6 +209,9 @@ func (s *APIKeyService) snapshotFromAPIKey(apiKey *APIKey) *APIKeyAuthSnapshot {
Quota: apiKey.Quota, Quota: apiKey.Quota,
QuotaUsed: apiKey.QuotaUsed, QuotaUsed: apiKey.QuotaUsed,
ExpiresAt: apiKey.ExpiresAt, ExpiresAt: apiKey.ExpiresAt,
RateLimit5h: apiKey.RateLimit5h,
RateLimit1d: apiKey.RateLimit1d,
RateLimit7d: apiKey.RateLimit7d,
User: APIKeyAuthUserSnapshot{ User: APIKeyAuthUserSnapshot{
ID: apiKey.User.ID, ID: apiKey.User.ID,
Status: apiKey.User.Status, Status: apiKey.User.Status,
@@ -262,6 +265,9 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
Quota: snapshot.Quota, Quota: snapshot.Quota,
QuotaUsed: snapshot.QuotaUsed, QuotaUsed: snapshot.QuotaUsed,
ExpiresAt: snapshot.ExpiresAt, ExpiresAt: snapshot.ExpiresAt,
RateLimit5h: snapshot.RateLimit5h,
RateLimit1d: snapshot.RateLimit1d,
RateLimit7d: snapshot.RateLimit7d,
User: &User{ User: &User{
ID: snapshot.User.ID, ID: snapshot.User.ID,
Status: snapshot.User.Status, Status: snapshot.User.Status,

View File

@@ -30,6 +30,11 @@ var (
ErrAPIKeyExpired = infraerrors.Forbidden("API_KEY_EXPIRED", "api key 已过期") ErrAPIKeyExpired = infraerrors.Forbidden("API_KEY_EXPIRED", "api key 已过期")
// ErrAPIKeyQuotaExhausted = infraerrors.TooManyRequests("API_KEY_QUOTA_EXHAUSTED", "api key quota exhausted") // ErrAPIKeyQuotaExhausted = infraerrors.TooManyRequests("API_KEY_QUOTA_EXHAUSTED", "api key quota exhausted")
ErrAPIKeyQuotaExhausted = infraerrors.TooManyRequests("API_KEY_QUOTA_EXHAUSTED", "api key 额度已用完") ErrAPIKeyQuotaExhausted = infraerrors.TooManyRequests("API_KEY_QUOTA_EXHAUSTED", "api key 额度已用完")
// Rate limit errors
ErrAPIKeyRateLimit5hExceeded = infraerrors.TooManyRequests("API_KEY_RATE_5H_EXCEEDED", "api key 5小时限额已用完")
ErrAPIKeyRateLimit1dExceeded = infraerrors.TooManyRequests("API_KEY_RATE_1D_EXCEEDED", "api key 日限额已用完")
ErrAPIKeyRateLimit7dExceeded = infraerrors.TooManyRequests("API_KEY_RATE_7D_EXCEEDED", "api key 7天限额已用完")
) )
const ( const (
@@ -64,6 +69,21 @@ type APIKeyRepository interface {
// Quota methods // Quota methods
IncrementQuotaUsed(ctx context.Context, id int64, amount float64) (float64, error) IncrementQuotaUsed(ctx context.Context, id int64, amount float64) (float64, error)
UpdateLastUsed(ctx context.Context, id int64, usedAt time.Time) error UpdateLastUsed(ctx context.Context, id int64, usedAt time.Time) error
// Rate limit methods
IncrementRateLimitUsage(ctx context.Context, id int64, cost float64) error
ResetRateLimitWindows(ctx context.Context, id int64) error
GetRateLimitData(ctx context.Context, id int64) (*APIKeyRateLimitData, error)
}
// APIKeyRateLimitData holds rate limit usage and window state for an API key.
type APIKeyRateLimitData struct {
Usage5h float64
Usage1d float64
Usage7d float64
Window5hStart *time.Time
Window1dStart *time.Time
Window7dStart *time.Time
} }
// APIKeyCache defines cache operations for API key service // APIKeyCache defines cache operations for API key service
@@ -102,6 +122,11 @@ type CreateAPIKeyRequest struct {
// Quota fields // Quota fields
Quota float64 `json:"quota"` // Quota limit in USD (0 = unlimited) Quota float64 `json:"quota"` // Quota limit in USD (0 = unlimited)
ExpiresInDays *int `json:"expires_in_days"` // Days until expiry (nil = never expires) ExpiresInDays *int `json:"expires_in_days"` // Days until expiry (nil = never expires)
// Rate limit fields (0 = unlimited)
RateLimit5h float64 `json:"rate_limit_5h"`
RateLimit1d float64 `json:"rate_limit_1d"`
RateLimit7d float64 `json:"rate_limit_7d"`
} }
// UpdateAPIKeyRequest 更新API Key请求 // UpdateAPIKeyRequest 更新API Key请求
@@ -117,22 +142,34 @@ type UpdateAPIKeyRequest struct {
ExpiresAt *time.Time `json:"expires_at"` // Expiration time (nil = no change) ExpiresAt *time.Time `json:"expires_at"` // Expiration time (nil = no change)
ClearExpiration bool `json:"-"` // Clear expiration (internal use) ClearExpiration bool `json:"-"` // Clear expiration (internal use)
ResetQuota *bool `json:"reset_quota"` // Reset quota_used to 0 ResetQuota *bool `json:"reset_quota"` // Reset quota_used to 0
// Rate limit fields (nil = no change, 0 = unlimited)
RateLimit5h *float64 `json:"rate_limit_5h"`
RateLimit1d *float64 `json:"rate_limit_1d"`
RateLimit7d *float64 `json:"rate_limit_7d"`
ResetRateLimitUsage *bool `json:"reset_rate_limit_usage"` // Reset all usage counters to 0
} }
// APIKeyService API Key服务 // APIKeyService API Key服务
// RateLimitCacheInvalidator invalidates rate limit cache entries on manual reset.
type RateLimitCacheInvalidator interface {
InvalidateAPIKeyRateLimit(ctx context.Context, keyID int64) error
}
type APIKeyService struct { type APIKeyService struct {
apiKeyRepo APIKeyRepository apiKeyRepo APIKeyRepository
userRepo UserRepository userRepo UserRepository
groupRepo GroupRepository groupRepo GroupRepository
userSubRepo UserSubscriptionRepository userSubRepo UserSubscriptionRepository
userGroupRateRepo UserGroupRateRepository userGroupRateRepo UserGroupRateRepository
cache APIKeyCache cache APIKeyCache
cfg *config.Config rateLimitCacheInvalid RateLimitCacheInvalidator // optional: invalidate Redis rate limit cache
authCacheL1 *ristretto.Cache cfg *config.Config
authCfg apiKeyAuthCacheConfig authCacheL1 *ristretto.Cache
authGroup singleflight.Group authCfg apiKeyAuthCacheConfig
lastUsedTouchL1 sync.Map // keyID -> nextAllowedAt(time.Time) authGroup singleflight.Group
lastUsedTouchSF singleflight.Group lastUsedTouchL1 sync.Map // keyID -> nextAllowedAt(time.Time)
lastUsedTouchSF singleflight.Group
} }
// NewAPIKeyService 创建API Key服务实例 // NewAPIKeyService 创建API Key服务实例
@@ -158,6 +195,12 @@ func NewAPIKeyService(
return svc return svc
} }
// SetRateLimitCacheInvalidator sets the optional rate limit cache invalidator.
// Called after construction (e.g. in wire) to avoid circular dependencies.
func (s *APIKeyService) SetRateLimitCacheInvalidator(inv RateLimitCacheInvalidator) {
s.rateLimitCacheInvalid = inv
}
func (s *APIKeyService) compileAPIKeyIPRules(apiKey *APIKey) { func (s *APIKeyService) compileAPIKeyIPRules(apiKey *APIKey) {
if apiKey == nil { if apiKey == nil {
return return
@@ -327,6 +370,9 @@ func (s *APIKeyService) Create(ctx context.Context, userID int64, req CreateAPIK
IPBlacklist: req.IPBlacklist, IPBlacklist: req.IPBlacklist,
Quota: req.Quota, Quota: req.Quota,
QuotaUsed: 0, QuotaUsed: 0,
RateLimit5h: req.RateLimit5h,
RateLimit1d: req.RateLimit1d,
RateLimit7d: req.RateLimit7d,
} }
// Set expiration time if specified // Set expiration time if specified
@@ -519,6 +565,26 @@ func (s *APIKeyService) Update(ctx context.Context, id int64, userID int64, req
apiKey.IPWhitelist = req.IPWhitelist apiKey.IPWhitelist = req.IPWhitelist
apiKey.IPBlacklist = req.IPBlacklist apiKey.IPBlacklist = req.IPBlacklist
// Update rate limit configuration
if req.RateLimit5h != nil {
apiKey.RateLimit5h = *req.RateLimit5h
}
if req.RateLimit1d != nil {
apiKey.RateLimit1d = *req.RateLimit1d
}
if req.RateLimit7d != nil {
apiKey.RateLimit7d = *req.RateLimit7d
}
resetRateLimit := req.ResetRateLimitUsage != nil && *req.ResetRateLimitUsage
if resetRateLimit {
apiKey.Usage5h = 0
apiKey.Usage1d = 0
apiKey.Usage7d = 0
apiKey.Window5hStart = nil
apiKey.Window1dStart = nil
apiKey.Window7dStart = nil
}
if err := s.apiKeyRepo.Update(ctx, apiKey); err != nil { if err := s.apiKeyRepo.Update(ctx, apiKey); err != nil {
return nil, fmt.Errorf("update api key: %w", err) return nil, fmt.Errorf("update api key: %w", err)
} }
@@ -526,6 +592,11 @@ func (s *APIKeyService) Update(ctx context.Context, id int64, userID int64, req
s.InvalidateAuthCacheByKey(ctx, apiKey.Key) s.InvalidateAuthCacheByKey(ctx, apiKey.Key)
s.compileAPIKeyIPRules(apiKey) s.compileAPIKeyIPRules(apiKey)
// Invalidate Redis rate limit cache so reset takes effect immediately
if resetRateLimit && s.rateLimitCacheInvalid != nil {
_ = s.rateLimitCacheInvalid.InvalidateAPIKeyRateLimit(ctx, apiKey.ID)
}
return apiKey, nil return apiKey, nil
} }
@@ -746,3 +817,11 @@ func (s *APIKeyService) UpdateQuotaUsed(ctx context.Context, apiKeyID int64, cos
return nil return nil
} }
// UpdateRateLimitUsage atomically increments rate limit usage counters in the DB.
func (s *APIKeyService) UpdateRateLimitUsage(ctx context.Context, apiKeyID int64, cost float64) error {
if cost <= 0 {
return nil
}
return s.apiKeyRepo.IncrementRateLimitUsage(ctx, apiKeyID, cost)
}

View File

@@ -40,6 +40,7 @@ const (
cacheWriteSetSubscription cacheWriteSetSubscription
cacheWriteUpdateSubscriptionUsage cacheWriteUpdateSubscriptionUsage
cacheWriteDeductBalance cacheWriteDeductBalance
cacheWriteUpdateRateLimitUsage
) )
// 异步缓存写入工作池配置 // 异步缓存写入工作池配置
@@ -68,19 +69,26 @@ type cacheWriteTask struct {
kind cacheWriteKind kind cacheWriteKind
userID int64 userID int64
groupID int64 groupID int64
apiKeyID int64
balance float64 balance float64
amount float64 amount float64
subscriptionData *subscriptionCacheData subscriptionData *subscriptionCacheData
} }
// apiKeyRateLimitLoader defines the interface for loading rate limit data from DB.
type apiKeyRateLimitLoader interface {
GetRateLimitData(ctx context.Context, keyID int64) (*APIKeyRateLimitData, error)
}
// BillingCacheService 计费缓存服务 // BillingCacheService 计费缓存服务
// 负责余额和订阅数据的缓存管理,提供高性能的计费资格检查 // 负责余额和订阅数据的缓存管理,提供高性能的计费资格检查
type BillingCacheService struct { type BillingCacheService struct {
cache BillingCache cache BillingCache
userRepo UserRepository userRepo UserRepository
subRepo UserSubscriptionRepository subRepo UserSubscriptionRepository
cfg *config.Config apiKeyRateLimitLoader apiKeyRateLimitLoader
circuitBreaker *billingCircuitBreaker cfg *config.Config
circuitBreaker *billingCircuitBreaker
cacheWriteChan chan cacheWriteTask cacheWriteChan chan cacheWriteTask
cacheWriteWg sync.WaitGroup cacheWriteWg sync.WaitGroup
@@ -96,12 +104,13 @@ type BillingCacheService struct {
} }
// NewBillingCacheService 创建计费缓存服务 // NewBillingCacheService 创建计费缓存服务
func NewBillingCacheService(cache BillingCache, userRepo UserRepository, subRepo UserSubscriptionRepository, cfg *config.Config) *BillingCacheService { func NewBillingCacheService(cache BillingCache, userRepo UserRepository, subRepo UserSubscriptionRepository, apiKeyRepo APIKeyRepository, cfg *config.Config) *BillingCacheService {
svc := &BillingCacheService{ svc := &BillingCacheService{
cache: cache, cache: cache,
userRepo: userRepo, userRepo: userRepo,
subRepo: subRepo, subRepo: subRepo,
cfg: cfg, apiKeyRateLimitLoader: apiKeyRepo,
cfg: cfg,
} }
svc.circuitBreaker = newBillingCircuitBreaker(cfg.Billing.CircuitBreaker) svc.circuitBreaker = newBillingCircuitBreaker(cfg.Billing.CircuitBreaker)
svc.startCacheWriteWorkers() svc.startCacheWriteWorkers()
@@ -188,6 +197,12 @@ func (s *BillingCacheService) cacheWriteWorker(ch <-chan cacheWriteTask) {
logger.LegacyPrintf("service.billing_cache", "Warning: deduct balance cache failed for user %d: %v", task.userID, err) logger.LegacyPrintf("service.billing_cache", "Warning: deduct balance cache failed for user %d: %v", task.userID, err)
} }
} }
case cacheWriteUpdateRateLimitUsage:
if s.cache != nil {
if err := s.cache.UpdateAPIKeyRateLimitUsage(ctx, task.apiKeyID, task.amount); err != nil {
logger.LegacyPrintf("service.billing_cache", "Warning: update rate limit usage cache failed for api key %d: %v", task.apiKeyID, err)
}
}
} }
cancel() cancel()
} }
@@ -204,6 +219,8 @@ func cacheWriteKindName(kind cacheWriteKind) string {
return "update_subscription_usage" return "update_subscription_usage"
case cacheWriteDeductBalance: case cacheWriteDeductBalance:
return "deduct_balance" return "deduct_balance"
case cacheWriteUpdateRateLimitUsage:
return "update_rate_limit_usage"
default: default:
return "unknown" return "unknown"
} }
@@ -476,6 +493,137 @@ func (s *BillingCacheService) InvalidateSubscription(ctx context.Context, userID
return nil return nil
} }
// ============================================
// API Key 限速缓存方法
// ============================================
// checkAPIKeyRateLimits checks rate limit windows for an API key.
// It loads usage from Redis cache (falling back to DB on cache miss),
// resets expired windows in-memory and triggers async DB reset,
// and returns an error if any window limit is exceeded.
func (s *BillingCacheService) checkAPIKeyRateLimits(ctx context.Context, apiKey *APIKey) error {
if s.cache == nil {
// No cache: fall back to reading from DB directly
if s.apiKeyRateLimitLoader == nil {
return nil
}
data, err := s.apiKeyRateLimitLoader.GetRateLimitData(ctx, apiKey.ID)
if err != nil {
return nil // Don't block requests on DB errors
}
return s.evaluateRateLimits(ctx, apiKey, data.Usage5h, data.Usage1d, data.Usage7d,
data.Window5hStart, data.Window1dStart, data.Window7dStart)
}
cacheData, err := s.cache.GetAPIKeyRateLimit(ctx, apiKey.ID)
if err != nil {
// Cache miss: load from DB and populate cache
if s.apiKeyRateLimitLoader == nil {
return nil
}
dbData, dbErr := s.apiKeyRateLimitLoader.GetRateLimitData(ctx, apiKey.ID)
if dbErr != nil {
return nil // Don't block requests on DB errors
}
// Build cache entry from DB data
cacheEntry := &APIKeyRateLimitCacheData{
Usage5h: dbData.Usage5h,
Usage1d: dbData.Usage1d,
Usage7d: dbData.Usage7d,
}
if dbData.Window5hStart != nil {
cacheEntry.Window5h = dbData.Window5hStart.Unix()
}
if dbData.Window1dStart != nil {
cacheEntry.Window1d = dbData.Window1dStart.Unix()
}
if dbData.Window7dStart != nil {
cacheEntry.Window7d = dbData.Window7dStart.Unix()
}
_ = s.cache.SetAPIKeyRateLimit(ctx, apiKey.ID, cacheEntry)
cacheData = cacheEntry
}
var w5h, w1d, w7d *time.Time
if cacheData.Window5h > 0 {
t := time.Unix(cacheData.Window5h, 0)
w5h = &t
}
if cacheData.Window1d > 0 {
t := time.Unix(cacheData.Window1d, 0)
w1d = &t
}
if cacheData.Window7d > 0 {
t := time.Unix(cacheData.Window7d, 0)
w7d = &t
}
return s.evaluateRateLimits(ctx, apiKey, cacheData.Usage5h, cacheData.Usage1d, cacheData.Usage7d, w5h, w1d, w7d)
}
// evaluateRateLimits checks usage against limits, triggering async resets for expired windows.
func (s *BillingCacheService) evaluateRateLimits(ctx context.Context, apiKey *APIKey, usage5h, usage1d, usage7d float64, w5h, w1d, w7d *time.Time) error {
needsReset := false
// Reset expired windows in-memory for check purposes
if w5h != nil && time.Since(*w5h) >= 5*time.Hour {
usage5h = 0
needsReset = true
}
if w1d != nil && time.Since(*w1d) >= 24*time.Hour {
usage1d = 0
needsReset = true
}
if w7d != nil && time.Since(*w7d) >= 7*24*time.Hour {
usage7d = 0
needsReset = true
}
// Trigger async DB reset if any window expired
if needsReset {
keyID := apiKey.ID
go func() {
resetCtx, cancel := context.WithTimeout(context.Background(), cacheWriteTimeout)
defer cancel()
if s.apiKeyRateLimitLoader != nil {
// Use the repo directly - reset then reload cache
if loader, ok := s.apiKeyRateLimitLoader.(interface {
ResetRateLimitWindows(ctx context.Context, id int64) error
}); ok {
_ = loader.ResetRateLimitWindows(resetCtx, keyID)
}
}
// Invalidate cache so next request loads fresh data
if s.cache != nil {
_ = s.cache.InvalidateAPIKeyRateLimit(resetCtx, keyID)
}
}()
}
// Check limits
if apiKey.RateLimit5h > 0 && usage5h >= apiKey.RateLimit5h {
return ErrAPIKeyRateLimit5hExceeded
}
if apiKey.RateLimit1d > 0 && usage1d >= apiKey.RateLimit1d {
return ErrAPIKeyRateLimit1dExceeded
}
if apiKey.RateLimit7d > 0 && usage7d >= apiKey.RateLimit7d {
return ErrAPIKeyRateLimit7dExceeded
}
return nil
}
// QueueUpdateAPIKeyRateLimitUsage asynchronously updates rate limit usage in the cache.
func (s *BillingCacheService) QueueUpdateAPIKeyRateLimitUsage(apiKeyID int64, cost float64) {
if s.cache == nil {
return
}
s.enqueueCacheWrite(cacheWriteTask{
kind: cacheWriteUpdateRateLimitUsage,
apiKeyID: apiKeyID,
amount: cost,
})
}
// ============================================ // ============================================
// 统一检查方法 // 统一检查方法
// ============================================ // ============================================
@@ -496,10 +644,23 @@ func (s *BillingCacheService) CheckBillingEligibility(ctx context.Context, user
isSubscriptionMode := group != nil && group.IsSubscriptionType() && subscription != nil isSubscriptionMode := group != nil && group.IsSubscriptionType() && subscription != nil
if isSubscriptionMode { if isSubscriptionMode {
return s.checkSubscriptionEligibility(ctx, user.ID, group, subscription) if err := s.checkSubscriptionEligibility(ctx, user.ID, group, subscription); err != nil {
return err
}
} else {
if err := s.checkBalanceEligibility(ctx, user.ID); err != nil {
return err
}
} }
return s.checkBalanceEligibility(ctx, user.ID) // Check API Key rate limits (applies to both billing modes)
if apiKey != nil && apiKey.HasRateLimits() {
if err := s.checkAPIKeyRateLimits(ctx, apiKey); err != nil {
return err
}
}
return nil
} }
// checkBalanceEligibility 检查余额模式资格 // checkBalanceEligibility 检查余额模式资格

View File

@@ -52,9 +52,25 @@ func (b *billingCacheWorkerStub) InvalidateSubscriptionCache(ctx context.Context
return nil return nil
} }
func (b *billingCacheWorkerStub) GetAPIKeyRateLimit(ctx context.Context, keyID int64) (*APIKeyRateLimitCacheData, error) {
return nil, errors.New("not implemented")
}
func (b *billingCacheWorkerStub) SetAPIKeyRateLimit(ctx context.Context, keyID int64, data *APIKeyRateLimitCacheData) error {
return nil
}
func (b *billingCacheWorkerStub) UpdateAPIKeyRateLimitUsage(ctx context.Context, keyID int64, cost float64) error {
return nil
}
func (b *billingCacheWorkerStub) InvalidateAPIKeyRateLimit(ctx context.Context, keyID int64) error {
return nil
}
func TestBillingCacheServiceQueueHighLoad(t *testing.T) { func TestBillingCacheServiceQueueHighLoad(t *testing.T) {
cache := &billingCacheWorkerStub{} cache := &billingCacheWorkerStub{}
svc := NewBillingCacheService(cache, nil, nil, &config.Config{}) svc := NewBillingCacheService(cache, nil, nil, nil, &config.Config{})
t.Cleanup(svc.Stop) t.Cleanup(svc.Stop)
start := time.Now() start := time.Now()
@@ -76,7 +92,7 @@ func TestBillingCacheServiceQueueHighLoad(t *testing.T) {
func TestBillingCacheServiceEnqueueAfterStopReturnsFalse(t *testing.T) { func TestBillingCacheServiceEnqueueAfterStopReturnsFalse(t *testing.T) {
cache := &billingCacheWorkerStub{} cache := &billingCacheWorkerStub{}
svc := NewBillingCacheService(cache, nil, nil, &config.Config{}) svc := NewBillingCacheService(cache, nil, nil, nil, &config.Config{})
svc.Stop() svc.Stop()
enqueued := svc.enqueueCacheWrite(cacheWriteTask{ enqueued := svc.enqueueCacheWrite(cacheWriteTask{

View File

@@ -10,6 +10,16 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
) )
// APIKeyRateLimitCacheData holds rate limit usage data cached in Redis.
type APIKeyRateLimitCacheData struct {
Usage5h float64 `json:"usage_5h"`
Usage1d float64 `json:"usage_1d"`
Usage7d float64 `json:"usage_7d"`
Window5h int64 `json:"window_5h"` // unix timestamp, 0 = not started
Window1d int64 `json:"window_1d"`
Window7d int64 `json:"window_7d"`
}
// BillingCache defines cache operations for billing service // BillingCache defines cache operations for billing service
type BillingCache interface { type BillingCache interface {
// Balance operations // Balance operations
@@ -23,6 +33,12 @@ type BillingCache interface {
SetSubscriptionCache(ctx context.Context, userID, groupID int64, data *SubscriptionCacheData) error SetSubscriptionCache(ctx context.Context, userID, groupID int64, data *SubscriptionCacheData) error
UpdateSubscriptionUsage(ctx context.Context, userID, groupID int64, cost float64) error UpdateSubscriptionUsage(ctx context.Context, userID, groupID int64, cost float64) error
InvalidateSubscriptionCache(ctx context.Context, userID, groupID int64) error InvalidateSubscriptionCache(ctx context.Context, userID, groupID int64) error
// API Key rate limit operations
GetAPIKeyRateLimit(ctx context.Context, keyID int64) (*APIKeyRateLimitCacheData, error)
SetAPIKeyRateLimit(ctx context.Context, keyID int64, data *APIKeyRateLimitCacheData) error
UpdateAPIKeyRateLimitUsage(ctx context.Context, keyID int64, cost float64) error
InvalidateAPIKeyRateLimit(ctx context.Context, keyID int64) error
} }
// ModelPricing 模型价格配置per-token价格与LiteLLM格式一致 // ModelPricing 模型价格配置per-token价格与LiteLLM格式一致

View File

@@ -6361,9 +6361,10 @@ type RecordUsageInput struct {
APIKeyService APIKeyQuotaUpdater // 可选用于更新API Key配额 APIKeyService APIKeyQuotaUpdater // 可选用于更新API Key配额
} }
// APIKeyQuotaUpdater defines the interface for updating API Key quota // APIKeyQuotaUpdater defines the interface for updating API Key quota and rate limit usage
type APIKeyQuotaUpdater interface { type APIKeyQuotaUpdater interface {
UpdateQuotaUsed(ctx context.Context, apiKeyID int64, cost float64) error UpdateQuotaUsed(ctx context.Context, apiKeyID int64, cost float64) error
UpdateRateLimitUsage(ctx context.Context, apiKeyID int64, cost float64) error
} }
// RecordUsage 记录使用量并扣费(或更新订阅用量) // RecordUsage 记录使用量并扣费(或更新订阅用量)
@@ -6557,6 +6558,14 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu
} }
} }
// Update API Key rate limit usage
if shouldBill && cost.ActualCost > 0 && apiKey.HasRateLimits() && input.APIKeyService != nil {
if err := input.APIKeyService.UpdateRateLimitUsage(ctx, apiKey.ID, cost.ActualCost); err != nil {
logger.LegacyPrintf("service.gateway", "Update API key rate limit usage failed: %v", err)
}
s.billingCacheService.QueueUpdateAPIKeyRateLimitUsage(apiKey.ID, cost.ActualCost)
}
// Schedule batch update for account last_used_at // Schedule batch update for account last_used_at
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)
@@ -6746,6 +6755,14 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input *
} }
} }
// Update API Key rate limit usage
if shouldBill && cost.ActualCost > 0 && apiKey.HasRateLimits() && input.APIKeyService != nil {
if err := input.APIKeyService.UpdateRateLimitUsage(ctx, apiKey.ID, cost.ActualCost); err != nil {
logger.LegacyPrintf("service.gateway", "Update API key rate limit usage failed: %v", err)
}
s.billingCacheService.QueueUpdateAPIKeyRateLimitUsage(apiKey.ID, cost.ActualCost)
}
// Schedule batch update for account last_used_at // Schedule batch update for account last_used_at
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)

View File

@@ -3492,6 +3492,14 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
} }
} }
// Update API Key rate limit usage
if shouldBill && cost.ActualCost > 0 && apiKey.HasRateLimits() && input.APIKeyService != nil {
if err := input.APIKeyService.UpdateRateLimitUsage(ctx, apiKey.ID, cost.ActualCost); err != nil {
logger.LegacyPrintf("service.openai_gateway", "Update API key rate limit usage failed: %v", err)
}
s.billingCacheService.QueueUpdateAPIKeyRateLimitUsage(apiKey.ID, cost.ActualCost)
}
// Schedule batch update for account last_used_at // Schedule batch update for account last_used_at
s.deferredService.ScheduleLastUsedUpdate(account.ID) s.deferredService.ScheduleLastUsedUpdate(account.ID)

View File

@@ -46,6 +46,7 @@ export async function getById(id: number): Promise<ApiKey> {
* @param ipBlacklist - Optional IP blacklist * @param ipBlacklist - Optional IP blacklist
* @param quota - Optional quota limit in USD (0 = unlimited) * @param quota - Optional quota limit in USD (0 = unlimited)
* @param expiresInDays - Optional days until expiry (undefined = never expires) * @param expiresInDays - Optional days until expiry (undefined = never expires)
* @param rateLimitData - Optional rate limit fields
* @returns Created API key * @returns Created API key
*/ */
export async function create( export async function create(
@@ -55,7 +56,8 @@ export async function create(
ipWhitelist?: string[], ipWhitelist?: string[],
ipBlacklist?: string[], ipBlacklist?: string[],
quota?: number, quota?: number,
expiresInDays?: number expiresInDays?: number,
rateLimitData?: { rate_limit_5h?: number; rate_limit_1d?: number; rate_limit_7d?: number }
): Promise<ApiKey> { ): Promise<ApiKey> {
const payload: CreateApiKeyRequest = { name } const payload: CreateApiKeyRequest = { name }
if (groupId !== undefined) { if (groupId !== undefined) {
@@ -76,6 +78,15 @@ export async function create(
if (expiresInDays !== undefined && expiresInDays > 0) { if (expiresInDays !== undefined && expiresInDays > 0) {
payload.expires_in_days = expiresInDays payload.expires_in_days = expiresInDays
} }
if (rateLimitData?.rate_limit_5h && rateLimitData.rate_limit_5h > 0) {
payload.rate_limit_5h = rateLimitData.rate_limit_5h
}
if (rateLimitData?.rate_limit_1d && rateLimitData.rate_limit_1d > 0) {
payload.rate_limit_1d = rateLimitData.rate_limit_1d
}
if (rateLimitData?.rate_limit_7d && rateLimitData.rate_limit_7d > 0) {
payload.rate_limit_7d = rateLimitData.rate_limit_7d
}
const { data } = await apiClient.post<ApiKey>('/keys', payload) const { data } = await apiClient.post<ApiKey>('/keys', payload)
return data return data

View File

@@ -560,6 +560,19 @@ export default {
resetQuotaConfirmMessage: 'Are you sure you want to reset the used quota (${used}) for key "{name}" to 0? This action cannot be undone.', resetQuotaConfirmMessage: 'Are you sure you want to reset the used quota (${used}) for key "{name}" to 0? This action cannot be undone.',
quotaResetSuccess: 'Quota reset successfully', quotaResetSuccess: 'Quota reset successfully',
failedToResetQuota: 'Failed to reset quota', failedToResetQuota: 'Failed to reset quota',
rateLimitColumn: 'Rate Limit',
rateLimitSection: 'Rate Limit',
resetUsage: 'Reset',
rateLimit5h: '5-Hour Limit (USD)',
rateLimit1d: 'Daily Limit (USD)',
rateLimit7d: '7-Day Limit (USD)',
rateLimitHint: 'Set the maximum spending for this key within each time window. 0 = unlimited.',
rateLimitUsage: 'Rate Limit Usage',
resetRateLimitUsage: 'Reset Rate Limit Usage',
resetRateLimitTitle: 'Confirm Reset Rate Limit',
resetRateLimitConfirmMessage: 'Are you sure you want to reset the rate limit usage for key "{name}"? All time window usage will be reset to zero. This action cannot be undone.',
rateLimitResetSuccess: 'Rate limit usage reset successfully',
failedToResetRateLimit: 'Failed to reset rate limit usage',
expiration: 'Expiration', expiration: 'Expiration',
expiresInDays: '{days} days', expiresInDays: '{days} days',
extendDays: '+{days} days', extendDays: '+{days} days',

View File

@@ -566,6 +566,19 @@ export default {
resetQuotaConfirmMessage: '确定要将密钥 "{name}" 的已用额度(${used})重置为 0 吗?此操作不可撤销。', resetQuotaConfirmMessage: '确定要将密钥 "{name}" 的已用额度(${used})重置为 0 吗?此操作不可撤销。',
quotaResetSuccess: '额度重置成功', quotaResetSuccess: '额度重置成功',
failedToResetQuota: '重置额度失败', failedToResetQuota: '重置额度失败',
rateLimitColumn: '速率限制',
rateLimitSection: '速率限制',
resetUsage: '重置',
rateLimit5h: '5小时限额 (USD)',
rateLimit1d: '日限额 (USD)',
rateLimit7d: '7天限额 (USD)',
rateLimitHint: '设置此密钥在指定时间窗口内的最大消费额。0 = 无限制。',
rateLimitUsage: '速率限制用量',
resetRateLimitUsage: '重置速率限制用量',
resetRateLimitTitle: '确认重置速率限制',
resetRateLimitConfirmMessage: '确定要重置密钥 "{name}" 的速率限制用量吗?所有时间窗口的已用额度将归零。此操作不可撤销。',
rateLimitResetSuccess: '速率限制已重置',
failedToResetRateLimit: '重置速率限制失败',
expiration: '密钥有效期', expiration: '密钥有效期',
expiresInDays: '{days} 天', expiresInDays: '{days} 天',
extendDays: '+{days} 天', extendDays: '+{days} 天',

View File

@@ -421,6 +421,15 @@ export interface ApiKey {
created_at: string created_at: string
updated_at: string updated_at: string
group?: Group group?: Group
rate_limit_5h: number
rate_limit_1d: number
rate_limit_7d: number
usage_5h: number
usage_1d: number
usage_7d: number
window_5h_start: string | null
window_1d_start: string | null
window_7d_start: string | null
} }
export interface CreateApiKeyRequest { export interface CreateApiKeyRequest {
@@ -431,6 +440,9 @@ export interface CreateApiKeyRequest {
ip_blacklist?: string[] ip_blacklist?: string[]
quota?: number // Quota limit in USD (0 = unlimited) quota?: number // Quota limit in USD (0 = unlimited)
expires_in_days?: number // Days until expiry (null = never expires) expires_in_days?: number // Days until expiry (null = never expires)
rate_limit_5h?: number
rate_limit_1d?: number
rate_limit_7d?: number
} }
export interface UpdateApiKeyRequest { export interface UpdateApiKeyRequest {
@@ -442,6 +454,10 @@ export interface UpdateApiKeyRequest {
quota?: number // Quota limit in USD (null = no change, 0 = unlimited) quota?: number // Quota limit in USD (null = no change, 0 = unlimited)
expires_at?: string | null // Expiration time (null = no change) expires_at?: string | null // Expiration time (null = no change)
reset_quota?: boolean // Reset quota_used to 0 reset_quota?: boolean // Reset quota_used to 0
rate_limit_5h?: number
rate_limit_1d?: number
rate_limit_7d?: number
reset_rate_limit_usage?: boolean
} }
export interface CreateGroupRequest { export interface CreateGroupRequest {

View File

@@ -137,6 +137,97 @@
</div> </div>
</template> </template>
<template #cell-rate_limit="{ row }">
<div v-if="row.rate_limit_5h > 0 || row.rate_limit_1d > 0 || row.rate_limit_7d > 0" class="space-y-1.5 min-w-[140px]">
<!-- 5h window -->
<div v-if="row.rate_limit_5h > 0">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500 dark:text-gray-400">5h</span>
<span :class="[
'font-medium tabular-nums',
row.usage_5h >= row.rate_limit_5h ? 'text-red-500' :
row.usage_5h >= row.rate_limit_5h * 0.8 ? 'text-yellow-500' :
'text-gray-700 dark:text-gray-300'
]">
${{ row.usage_5h?.toFixed(2) || '0.00' }}/${{ row.rate_limit_5h?.toFixed(2) }}
</span>
</div>
<div class="h-1 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
row.usage_5h >= row.rate_limit_5h ? 'bg-red-500' :
row.usage_5h >= row.rate_limit_5h * 0.8 ? 'bg-yellow-500' :
'bg-emerald-500'
]"
:style="{ width: Math.min((row.usage_5h / row.rate_limit_5h) * 100, 100) + '%' }"
/>
</div>
</div>
<!-- 1d window -->
<div v-if="row.rate_limit_1d > 0">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500 dark:text-gray-400">1d</span>
<span :class="[
'font-medium tabular-nums',
row.usage_1d >= row.rate_limit_1d ? 'text-red-500' :
row.usage_1d >= row.rate_limit_1d * 0.8 ? 'text-yellow-500' :
'text-gray-700 dark:text-gray-300'
]">
${{ row.usage_1d?.toFixed(2) || '0.00' }}/${{ row.rate_limit_1d?.toFixed(2) }}
</span>
</div>
<div class="h-1 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
row.usage_1d >= row.rate_limit_1d ? 'bg-red-500' :
row.usage_1d >= row.rate_limit_1d * 0.8 ? 'bg-yellow-500' :
'bg-emerald-500'
]"
:style="{ width: Math.min((row.usage_1d / row.rate_limit_1d) * 100, 100) + '%' }"
/>
</div>
</div>
<!-- 7d window -->
<div v-if="row.rate_limit_7d > 0">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500 dark:text-gray-400">7d</span>
<span :class="[
'font-medium tabular-nums',
row.usage_7d >= row.rate_limit_7d ? 'text-red-500' :
row.usage_7d >= row.rate_limit_7d * 0.8 ? 'text-yellow-500' :
'text-gray-700 dark:text-gray-300'
]">
${{ row.usage_7d?.toFixed(2) || '0.00' }}/${{ row.rate_limit_7d?.toFixed(2) }}
</span>
</div>
<div class="h-1 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
row.usage_7d >= row.rate_limit_7d ? 'bg-red-500' :
row.usage_7d >= row.rate_limit_7d * 0.8 ? 'bg-yellow-500' :
'bg-emerald-500'
]"
:style="{ width: Math.min((row.usage_7d / row.rate_limit_7d) * 100, 100) + '%' }"
/>
</div>
</div>
<!-- Reset button -->
<button
v-if="row.usage_5h > 0 || row.usage_1d > 0 || row.usage_7d > 0"
@click.stop="confirmResetRateLimitFromTable(row)"
class="mt-0.5 inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
:title="t('keys.resetRateLimitUsage')"
>
<Icon name="refresh" size="xs" />
{{ t('keys.resetUsage') }}
</button>
</div>
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
</template>
<template #cell-expires_at="{ value }"> <template #cell-expires_at="{ value }">
<span v-if="value" :class="[ <span v-if="value" :class="[
'text-sm', 'text-sm',
@@ -452,6 +543,180 @@
</div> </div>
</div> </div>
<!-- Rate Limit Section -->
<div class="space-y-3">
<div class="flex items-center justify-between">
<label class="input-label mb-0">{{ t('keys.rateLimitSection') }}</label>
<button
type="button"
@click="formData.enable_rate_limit = !formData.enable_rate_limit"
:class="[
'relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none',
formData.enable_rate_limit ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
]"
>
<span
:class="[
'pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
formData.enable_rate_limit ? 'translate-x-4' : 'translate-x-0'
]"
/>
</button>
</div>
<div v-if="formData.enable_rate_limit" class="space-y-4 pt-2">
<p class="input-hint -mt-2">{{ t('keys.rateLimitHint') }}</p>
<!-- 5-Hour Limit -->
<div>
<label class="input-label">{{ t('keys.rateLimit5h') }}</label>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">$</span>
<input
v-model.number="formData.rate_limit_5h"
type="number"
step="0.01"
min="0"
class="input pl-7"
:placeholder="'0'"
/>
</div>
<!-- Usage info (edit mode only) -->
<div v-if="showEditModal && selectedKey && selectedKey.rate_limit_5h > 0" class="mt-2">
<div class="flex items-center gap-2">
<div class="flex-1 rounded-lg bg-gray-100 px-3 py-2 dark:bg-dark-700 text-sm">
<span :class="[
'font-medium',
selectedKey.usage_5h >= selectedKey.rate_limit_5h ? 'text-red-500' :
selectedKey.usage_5h >= selectedKey.rate_limit_5h * 0.8 ? 'text-yellow-500' :
'text-gray-900 dark:text-white'
]">
${{ selectedKey.usage_5h?.toFixed(4) || '0.0000' }}
</span>
<span class="mx-2 text-gray-400">/</span>
<span class="text-gray-500 dark:text-gray-400">
${{ selectedKey.rate_limit_5h?.toFixed(2) || '0.00' }}
</span>
</div>
</div>
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
selectedKey.usage_5h >= selectedKey.rate_limit_5h ? 'bg-red-500' :
selectedKey.usage_5h >= selectedKey.rate_limit_5h * 0.8 ? 'bg-yellow-500' :
'bg-green-500'
]"
:style="{ width: Math.min((selectedKey.usage_5h / selectedKey.rate_limit_5h) * 100, 100) + '%' }"
/>
</div>
</div>
</div>
<!-- Daily Limit -->
<div>
<label class="input-label">{{ t('keys.rateLimit1d') }}</label>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">$</span>
<input
v-model.number="formData.rate_limit_1d"
type="number"
step="0.01"
min="0"
class="input pl-7"
:placeholder="'0'"
/>
</div>
<!-- Usage info (edit mode only) -->
<div v-if="showEditModal && selectedKey && selectedKey.rate_limit_1d > 0" class="mt-2">
<div class="flex items-center gap-2">
<div class="flex-1 rounded-lg bg-gray-100 px-3 py-2 dark:bg-dark-700 text-sm">
<span :class="[
'font-medium',
selectedKey.usage_1d >= selectedKey.rate_limit_1d ? 'text-red-500' :
selectedKey.usage_1d >= selectedKey.rate_limit_1d * 0.8 ? 'text-yellow-500' :
'text-gray-900 dark:text-white'
]">
${{ selectedKey.usage_1d?.toFixed(4) || '0.0000' }}
</span>
<span class="mx-2 text-gray-400">/</span>
<span class="text-gray-500 dark:text-gray-400">
${{ selectedKey.rate_limit_1d?.toFixed(2) || '0.00' }}
</span>
</div>
</div>
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
selectedKey.usage_1d >= selectedKey.rate_limit_1d ? 'bg-red-500' :
selectedKey.usage_1d >= selectedKey.rate_limit_1d * 0.8 ? 'bg-yellow-500' :
'bg-green-500'
]"
:style="{ width: Math.min((selectedKey.usage_1d / selectedKey.rate_limit_1d) * 100, 100) + '%' }"
/>
</div>
</div>
</div>
<!-- 7-Day Limit -->
<div>
<label class="input-label">{{ t('keys.rateLimit7d') }}</label>
<div class="relative">
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500">$</span>
<input
v-model.number="formData.rate_limit_7d"
type="number"
step="0.01"
min="0"
class="input pl-7"
:placeholder="'0'"
/>
</div>
<!-- Usage info (edit mode only) -->
<div v-if="showEditModal && selectedKey && selectedKey.rate_limit_7d > 0" class="mt-2">
<div class="flex items-center gap-2">
<div class="flex-1 rounded-lg bg-gray-100 px-3 py-2 dark:bg-dark-700 text-sm">
<span :class="[
'font-medium',
selectedKey.usage_7d >= selectedKey.rate_limit_7d ? 'text-red-500' :
selectedKey.usage_7d >= selectedKey.rate_limit_7d * 0.8 ? 'text-yellow-500' :
'text-gray-900 dark:text-white'
]">
${{ selectedKey.usage_7d?.toFixed(4) || '0.0000' }}
</span>
<span class="mx-2 text-gray-400">/</span>
<span class="text-gray-500 dark:text-gray-400">
${{ selectedKey.rate_limit_7d?.toFixed(2) || '0.00' }}
</span>
</div>
</div>
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-dark-600">
<div
:class="[
'h-full rounded-full transition-all',
selectedKey.usage_7d >= selectedKey.rate_limit_7d ? 'bg-red-500' :
selectedKey.usage_7d >= selectedKey.rate_limit_7d * 0.8 ? 'bg-yellow-500' :
'bg-green-500'
]"
:style="{ width: Math.min((selectedKey.usage_7d / selectedKey.rate_limit_7d) * 100, 100) + '%' }"
/>
</div>
</div>
</div>
<!-- Reset Rate Limit button (edit mode only) -->
<div v-if="showEditModal && selectedKey && (selectedKey.rate_limit_5h > 0 || selectedKey.rate_limit_1d > 0 || selectedKey.rate_limit_7d > 0)">
<button
type="button"
@click="confirmResetRateLimit"
class="btn btn-secondary text-sm"
>
{{ t('keys.resetRateLimitUsage') }}
</button>
</div>
</div>
</div>
<!-- Expiration Section --> <!-- Expiration Section -->
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -593,6 +858,18 @@
@cancel="showResetQuotaDialog = false" @cancel="showResetQuotaDialog = false"
/> />
<!-- Reset Rate Limit Confirmation Dialog -->
<ConfirmDialog
:show="showResetRateLimitDialog"
:title="t('keys.resetRateLimitTitle')"
:message="t('keys.resetRateLimitConfirmMessage', { name: selectedKey?.name })"
:confirm-text="t('keys.reset')"
:cancel-text="t('common.cancel')"
:danger="true"
@confirm="resetRateLimitUsage"
@cancel="showResetRateLimitDialog = false"
/>
<!-- Use Key Modal --> <!-- Use Key Modal -->
<UseKeyModal <UseKeyModal
:show="showUseKeyModal" :show="showUseKeyModal"
@@ -743,6 +1020,7 @@ const columns = computed<Column[]>(() => [
{ key: 'key', label: t('keys.apiKey'), sortable: false }, { key: 'key', label: t('keys.apiKey'), sortable: false },
{ key: 'group', label: t('keys.group'), sortable: false }, { key: 'group', label: t('keys.group'), sortable: false },
{ key: 'usage', label: t('keys.usage'), sortable: false }, { key: 'usage', label: t('keys.usage'), sortable: false },
{ key: 'rate_limit', label: t('keys.rateLimitColumn'), sortable: false },
{ key: 'expires_at', label: t('keys.expiresAt'), sortable: true }, { key: 'expires_at', label: t('keys.expiresAt'), sortable: true },
{ key: 'status', label: t('common.status'), sortable: true }, { key: 'status', label: t('common.status'), sortable: true },
{ key: 'last_used_at', label: t('keys.lastUsedAt'), sortable: true }, { key: 'last_used_at', label: t('keys.lastUsedAt'), sortable: true },
@@ -768,6 +1046,7 @@ const showCreateModal = ref(false)
const showEditModal = ref(false) const showEditModal = ref(false)
const showDeleteDialog = ref(false) const showDeleteDialog = ref(false)
const showResetQuotaDialog = ref(false) const showResetQuotaDialog = ref(false)
const showResetRateLimitDialog = ref(false)
const showUseKeyModal = ref(false) const showUseKeyModal = ref(false)
const showCcsClientSelect = ref(false) const showCcsClientSelect = ref(false)
const pendingCcsRow = ref<ApiKey | null>(null) const pendingCcsRow = ref<ApiKey | null>(null)
@@ -806,6 +1085,11 @@ const formData = ref({
// Quota settings (empty = unlimited) // Quota settings (empty = unlimited)
enable_quota: false, enable_quota: false,
quota: null as number | null, quota: null as number | null,
// Rate limit settings
enable_rate_limit: false,
rate_limit_5h: null as number | null,
rate_limit_1d: null as number | null,
rate_limit_7d: null as number | null,
enable_expiration: false, enable_expiration: false,
expiration_preset: '30' as '7' | '30' | '90' | 'custom', expiration_preset: '30' as '7' | '30' | '90' | 'custom',
expiration_date: '' expiration_date: ''
@@ -966,6 +1250,10 @@ const editKey = (key: ApiKey) => {
ip_blacklist: (key.ip_blacklist || []).join('\n'), ip_blacklist: (key.ip_blacklist || []).join('\n'),
enable_quota: key.quota > 0, enable_quota: key.quota > 0,
quota: key.quota > 0 ? key.quota : null, quota: key.quota > 0 ? key.quota : null,
enable_rate_limit: (key.rate_limit_5h > 0) || (key.rate_limit_1d > 0) || (key.rate_limit_7d > 0),
rate_limit_5h: key.rate_limit_5h || null,
rate_limit_1d: key.rate_limit_1d || null,
rate_limit_7d: key.rate_limit_7d || null,
enable_expiration: hasExpiration, enable_expiration: hasExpiration,
expiration_preset: 'custom', expiration_preset: 'custom',
expiration_date: key.expires_at ? formatDateTimeLocal(key.expires_at) : '' expiration_date: key.expires_at ? formatDateTimeLocal(key.expires_at) : ''
@@ -1078,6 +1366,13 @@ const handleSubmit = async () => {
expiresAt = '' expiresAt = ''
} }
// Calculate rate limit values (send 0 when toggle is off)
const rateLimitData = formData.value.enable_rate_limit ? {
rate_limit_5h: formData.value.rate_limit_5h && formData.value.rate_limit_5h > 0 ? formData.value.rate_limit_5h : 0,
rate_limit_1d: formData.value.rate_limit_1d && formData.value.rate_limit_1d > 0 ? formData.value.rate_limit_1d : 0,
rate_limit_7d: formData.value.rate_limit_7d && formData.value.rate_limit_7d > 0 ? formData.value.rate_limit_7d : 0,
} : { rate_limit_5h: 0, rate_limit_1d: 0, rate_limit_7d: 0 }
submitting.value = true submitting.value = true
try { try {
if (showEditModal.value && selectedKey.value) { if (showEditModal.value && selectedKey.value) {
@@ -1088,7 +1383,10 @@ const handleSubmit = async () => {
ip_whitelist: ipWhitelist, ip_whitelist: ipWhitelist,
ip_blacklist: ipBlacklist, ip_blacklist: ipBlacklist,
quota: quota, quota: quota,
expires_at: expiresAt expires_at: expiresAt,
rate_limit_5h: rateLimitData.rate_limit_5h,
rate_limit_1d: rateLimitData.rate_limit_1d,
rate_limit_7d: rateLimitData.rate_limit_7d,
}) })
appStore.showSuccess(t('keys.keyUpdatedSuccess')) appStore.showSuccess(t('keys.keyUpdatedSuccess'))
} else { } else {
@@ -1100,7 +1398,8 @@ const handleSubmit = async () => {
ipWhitelist, ipWhitelist,
ipBlacklist, ipBlacklist,
quota, quota,
expiresInDays expiresInDays,
rateLimitData
) )
appStore.showSuccess(t('keys.keyCreatedSuccess')) appStore.showSuccess(t('keys.keyCreatedSuccess'))
// Only advance tour if active, on submit step, and creation succeeded // Only advance tour if active, on submit step, and creation succeeded
@@ -1154,6 +1453,10 @@ const closeModals = () => {
ip_blacklist: '', ip_blacklist: '',
enable_quota: false, enable_quota: false,
quota: null, quota: null,
enable_rate_limit: false,
rate_limit_5h: null,
rate_limit_1d: null,
rate_limit_7d: null,
enable_expiration: false, enable_expiration: false,
expiration_preset: '30', expiration_preset: '30',
expiration_date: '' expiration_date: ''
@@ -1190,6 +1493,37 @@ const resetQuotaUsed = async () => {
} }
} }
// Show reset rate limit confirmation dialog (from edit modal)
const confirmResetRateLimit = () => {
showResetRateLimitDialog.value = true
}
// Show reset rate limit confirmation dialog (from table row)
const confirmResetRateLimitFromTable = (row: ApiKey) => {
selectedKey.value = row
showResetRateLimitDialog.value = true
}
// Reset rate limit usage for an API key
const resetRateLimitUsage = async () => {
if (!selectedKey.value) return
showResetRateLimitDialog.value = false
try {
await keysAPI.update(selectedKey.value.id, { reset_rate_limit_usage: true })
appStore.showSuccess(t('keys.rateLimitResetSuccess'))
// Refresh key data
await loadApiKeys()
// Update the editing key with fresh data
const refreshedKey = apiKeys.value.find(k => k.id === selectedKey.value!.id)
if (refreshedKey) {
selectedKey.value = refreshedKey
}
} catch (error: any) {
const errorMsg = error.response?.data?.detail || t('keys.failedToResetRateLimit')
appStore.showError(errorMsg)
}
}
const importToCcswitch = (row: ApiKey) => { const importToCcswitch = (row: ApiKey) => {
const platform = row.group?.platform || 'anthropic' const platform = row.group?.platform || 'anthropic'