Commit 2b528c5f authored by LLLLLLiulei's avatar LLLLLLiulei
Browse files

feat: auto-pause expired accounts

parent d5ba7b80
...@@ -63,6 +63,7 @@ func provideCleanup( ...@@ -63,6 +63,7 @@ func provideCleanup(
entClient *ent.Client, entClient *ent.Client,
rdb *redis.Client, rdb *redis.Client,
tokenRefresh *service.TokenRefreshService, tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
pricing *service.PricingService, pricing *service.PricingService,
emailQueue *service.EmailQueueService, emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService, billingCache *service.BillingCacheService,
...@@ -84,6 +85,10 @@ func provideCleanup( ...@@ -84,6 +85,10 @@ func provideCleanup(
tokenRefresh.Stop() tokenRefresh.Stop()
return nil return nil
}}, }},
{"AccountExpiryService", func() error {
accountExpiry.Stop()
return nil
}},
{"PricingService", func() error { {"PricingService", func() error {
pricing.Stop() pricing.Stop()
return nil return nil
......
...@@ -87,6 +87,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { ...@@ -87,6 +87,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig) geminiOAuthClient := repository.NewGeminiOAuthClient(configConfig)
geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient() geminiCliCodeAssistClient := repository.NewGeminiCliCodeAssistClient()
geminiOAuthService := service.NewGeminiOAuthService(proxyRepository, geminiOAuthClient, geminiCliCodeAssistClient, configConfig) geminiOAuthService := service.NewGeminiOAuthService(proxyRepository, geminiOAuthClient, geminiCliCodeAssistClient, configConfig)
antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository) geminiQuotaService := service.NewGeminiQuotaService(configConfig, settingRepository)
tempUnschedCache := repository.NewTempUnschedCache(redisClient) tempUnschedCache := repository.NewTempUnschedCache(redisClient)
rateLimitService := service.NewRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache) rateLimitService := service.NewRateLimitService(accountRepository, usageLogRepository, configConfig, geminiQuotaService, tempUnschedCache)
...@@ -97,13 +98,12 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { ...@@ -97,13 +98,12 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
geminiTokenCache := repository.NewGeminiTokenCache(redisClient) geminiTokenCache := repository.NewGeminiTokenCache(redisClient)
geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService) geminiTokenProvider := service.NewGeminiTokenProvider(accountRepository, geminiTokenCache, geminiOAuthService)
gatewayCache := repository.NewGatewayCache(redisClient) gatewayCache := repository.NewGatewayCache(redisClient)
antigravityOAuthService := service.NewAntigravityOAuthService(proxyRepository)
antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService) antigravityTokenProvider := service.NewAntigravityTokenProvider(accountRepository, geminiTokenCache, antigravityOAuthService)
httpUpstream := repository.NewHTTPUpstream(configConfig) httpUpstream := repository.NewHTTPUpstream(configConfig)
antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, antigravityTokenProvider, rateLimitService, httpUpstream, settingService) antigravityGatewayService := service.NewAntigravityGatewayService(accountRepository, gatewayCache, antigravityTokenProvider, rateLimitService, httpUpstream, settingService)
accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig) accountTestService := service.NewAccountTestService(accountRepository, geminiTokenProvider, antigravityGatewayService, httpUpstream, configConfig)
concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig) concurrencyCache := repository.ProvideConcurrencyCache(redisClient, configConfig)
concurrencyService := service.NewConcurrencyService(concurrencyCache) concurrencyService := service.ProvideConcurrencyService(concurrencyCache, accountRepository, configConfig)
crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig) crsSyncService := service.NewCRSSyncService(accountRepository, proxyRepository, oAuthService, openAIOAuthService, geminiOAuthService, configConfig)
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService) accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService, crsSyncService)
oAuthHandler := admin.NewOAuthHandler(oAuthService) oAuthHandler := admin.NewOAuthHandler(oAuthService)
...@@ -148,7 +148,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { ...@@ -148,7 +148,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService) engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService)
httpServer := server.ProvideHTTPServer(configConfig, engine) httpServer := server.ProvideHTTPServer(configConfig, engine)
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, configConfig) tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, configConfig)
v := provideCleanup(client, redisClient, tokenRefreshService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService) accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
v := provideCleanup(client, redisClient, tokenRefreshService, accountExpiryService, pricingService, emailQueueService, billingCacheService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService)
application := &Application{ application := &Application{
Server: httpServer, Server: httpServer,
Cleanup: v, Cleanup: v,
...@@ -174,6 +175,7 @@ func provideCleanup( ...@@ -174,6 +175,7 @@ func provideCleanup(
entClient *ent.Client, entClient *ent.Client,
rdb *redis.Client, rdb *redis.Client,
tokenRefresh *service.TokenRefreshService, tokenRefresh *service.TokenRefreshService,
accountExpiry *service.AccountExpiryService,
pricing *service.PricingService, pricing *service.PricingService,
emailQueue *service.EmailQueueService, emailQueue *service.EmailQueueService,
billingCache *service.BillingCacheService, billingCache *service.BillingCacheService,
...@@ -194,6 +196,10 @@ func provideCleanup( ...@@ -194,6 +196,10 @@ func provideCleanup(
tokenRefresh.Stop() tokenRefresh.Stop()
return nil return nil
}}, }},
{"AccountExpiryService", func() error {
accountExpiry.Stop()
return nil
}},
{"PricingService", func() error { {"PricingService", func() error {
pricing.Stop() pricing.Stop()
return nil return nil
......
...@@ -49,6 +49,10 @@ type Account struct { ...@@ -49,6 +49,10 @@ type Account struct {
ErrorMessage *string `json:"error_message,omitempty"` ErrorMessage *string `json:"error_message,omitempty"`
// LastUsedAt holds the value of the "last_used_at" field. // LastUsedAt holds the value of the "last_used_at" field.
LastUsedAt *time.Time `json:"last_used_at,omitempty"` LastUsedAt *time.Time `json:"last_used_at,omitempty"`
// Account expiration time (NULL means no expiration).
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// Auto pause scheduling when account expires.
AutoPauseOnExpired bool `json:"auto_pause_on_expired,omitempty"`
// Schedulable holds the value of the "schedulable" field. // Schedulable holds the value of the "schedulable" field.
Schedulable bool `json:"schedulable,omitempty"` Schedulable bool `json:"schedulable,omitempty"`
// RateLimitedAt holds the value of the "rate_limited_at" field. // RateLimitedAt holds the value of the "rate_limited_at" field.
...@@ -129,13 +133,13 @@ func (*Account) scanValues(columns []string) ([]any, error) { ...@@ -129,13 +133,13 @@ func (*Account) scanValues(columns []string) ([]any, error) {
switch columns[i] { switch columns[i] {
case account.FieldCredentials, account.FieldExtra: case account.FieldCredentials, account.FieldExtra:
values[i] = new([]byte) values[i] = new([]byte)
case account.FieldSchedulable: case account.FieldAutoPauseOnExpired, account.FieldSchedulable:
values[i] = new(sql.NullBool) values[i] = new(sql.NullBool)
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority: case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldPriority:
values[i] = new(sql.NullInt64) values[i] = new(sql.NullInt64)
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus: case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldSessionWindowStatus:
values[i] = new(sql.NullString) values[i] = new(sql.NullString)
case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd: case account.FieldCreatedAt, account.FieldUpdatedAt, account.FieldDeletedAt, account.FieldLastUsedAt, account.FieldExpiresAt, account.FieldRateLimitedAt, account.FieldRateLimitResetAt, account.FieldOverloadUntil, account.FieldSessionWindowStart, account.FieldSessionWindowEnd:
values[i] = new(sql.NullTime) values[i] = new(sql.NullTime)
default: default:
values[i] = new(sql.UnknownType) values[i] = new(sql.UnknownType)
...@@ -257,6 +261,19 @@ func (_m *Account) assignValues(columns []string, values []any) error { ...@@ -257,6 +261,19 @@ func (_m *Account) assignValues(columns []string, values []any) error {
_m.LastUsedAt = new(time.Time) _m.LastUsedAt = new(time.Time)
*_m.LastUsedAt = value.Time *_m.LastUsedAt = value.Time
} }
case account.FieldExpiresAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field expires_at", values[i])
} else if value.Valid {
_m.ExpiresAt = new(time.Time)
*_m.ExpiresAt = value.Time
}
case account.FieldAutoPauseOnExpired:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field auto_pause_on_expired", values[i])
} else if value.Valid {
_m.AutoPauseOnExpired = value.Bool
}
case account.FieldSchedulable: case account.FieldSchedulable:
if value, ok := values[i].(*sql.NullBool); !ok { if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field schedulable", values[i]) return fmt.Errorf("unexpected type %T for field schedulable", values[i])
...@@ -416,6 +433,14 @@ func (_m *Account) String() string { ...@@ -416,6 +433,14 @@ func (_m *Account) String() string {
builder.WriteString(v.Format(time.ANSIC)) builder.WriteString(v.Format(time.ANSIC))
} }
builder.WriteString(", ") builder.WriteString(", ")
if v := _m.ExpiresAt; v != nil {
builder.WriteString("expires_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("auto_pause_on_expired=")
builder.WriteString(fmt.Sprintf("%v", _m.AutoPauseOnExpired))
builder.WriteString(", ")
builder.WriteString("schedulable=") builder.WriteString("schedulable=")
builder.WriteString(fmt.Sprintf("%v", _m.Schedulable)) builder.WriteString(fmt.Sprintf("%v", _m.Schedulable))
builder.WriteString(", ") builder.WriteString(", ")
......
...@@ -45,6 +45,10 @@ const ( ...@@ -45,6 +45,10 @@ const (
FieldErrorMessage = "error_message" FieldErrorMessage = "error_message"
// FieldLastUsedAt holds the string denoting the last_used_at field in the database. // FieldLastUsedAt holds the string denoting the last_used_at field in the database.
FieldLastUsedAt = "last_used_at" FieldLastUsedAt = "last_used_at"
// FieldExpiresAt holds the string denoting the expires_at field in the database.
FieldExpiresAt = "expires_at"
// FieldAutoPauseOnExpired holds the string denoting the auto_pause_on_expired field in the database.
FieldAutoPauseOnExpired = "auto_pause_on_expired"
// FieldSchedulable holds the string denoting the schedulable field in the database. // FieldSchedulable holds the string denoting the schedulable field in the database.
FieldSchedulable = "schedulable" FieldSchedulable = "schedulable"
// FieldRateLimitedAt holds the string denoting the rate_limited_at field in the database. // FieldRateLimitedAt holds the string denoting the rate_limited_at field in the database.
...@@ -115,6 +119,8 @@ var Columns = []string{ ...@@ -115,6 +119,8 @@ var Columns = []string{
FieldStatus, FieldStatus,
FieldErrorMessage, FieldErrorMessage,
FieldLastUsedAt, FieldLastUsedAt,
FieldExpiresAt,
FieldAutoPauseOnExpired,
FieldSchedulable, FieldSchedulable,
FieldRateLimitedAt, FieldRateLimitedAt,
FieldRateLimitResetAt, FieldRateLimitResetAt,
...@@ -172,6 +178,8 @@ var ( ...@@ -172,6 +178,8 @@ var (
DefaultStatus string DefaultStatus string
// StatusValidator is a validator for the "status" field. It is called by the builders before save. // StatusValidator is a validator for the "status" field. It is called by the builders before save.
StatusValidator func(string) error StatusValidator func(string) error
// DefaultAutoPauseOnExpired holds the default value on creation for the "auto_pause_on_expired" field.
DefaultAutoPauseOnExpired bool
// DefaultSchedulable holds the default value on creation for the "schedulable" field. // DefaultSchedulable holds the default value on creation for the "schedulable" field.
DefaultSchedulable bool DefaultSchedulable bool
// SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save. // SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
...@@ -251,6 +259,16 @@ func ByLastUsedAt(opts ...sql.OrderTermOption) OrderOption { ...@@ -251,6 +259,16 @@ func ByLastUsedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldLastUsedAt, opts...).ToFunc() return sql.OrderByField(FieldLastUsedAt, opts...).ToFunc()
} }
// ByExpiresAt orders the results by the expires_at field.
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
}
// ByAutoPauseOnExpired orders the results by the auto_pause_on_expired field.
func ByAutoPauseOnExpired(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldAutoPauseOnExpired, opts...).ToFunc()
}
// BySchedulable orders the results by the schedulable field. // BySchedulable orders the results by the schedulable field.
func BySchedulable(opts ...sql.OrderTermOption) OrderOption { func BySchedulable(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldSchedulable, opts...).ToFunc() return sql.OrderByField(FieldSchedulable, opts...).ToFunc()
......
...@@ -120,6 +120,16 @@ func LastUsedAt(v time.Time) predicate.Account { ...@@ -120,6 +120,16 @@ func LastUsedAt(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldLastUsedAt, v)) return predicate.Account(sql.FieldEQ(FieldLastUsedAt, v))
} }
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
func ExpiresAt(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldExpiresAt, v))
}
// AutoPauseOnExpired applies equality check predicate on the "auto_pause_on_expired" field. It's identical to AutoPauseOnExpiredEQ.
func AutoPauseOnExpired(v bool) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldAutoPauseOnExpired, v))
}
// Schedulable applies equality check predicate on the "schedulable" field. It's identical to SchedulableEQ. // Schedulable applies equality check predicate on the "schedulable" field. It's identical to SchedulableEQ.
func Schedulable(v bool) predicate.Account { func Schedulable(v bool) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSchedulable, v)) return predicate.Account(sql.FieldEQ(FieldSchedulable, v))
...@@ -855,6 +865,66 @@ func LastUsedAtNotNil() predicate.Account { ...@@ -855,6 +865,66 @@ func LastUsedAtNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldLastUsedAt)) return predicate.Account(sql.FieldNotNull(FieldLastUsedAt))
} }
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
func ExpiresAtEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldExpiresAt, v))
}
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
func ExpiresAtNEQ(v time.Time) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldExpiresAt, v))
}
// ExpiresAtIn applies the In predicate on the "expires_at" field.
func ExpiresAtIn(vs ...time.Time) predicate.Account {
return predicate.Account(sql.FieldIn(FieldExpiresAt, vs...))
}
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
func ExpiresAtNotIn(vs ...time.Time) predicate.Account {
return predicate.Account(sql.FieldNotIn(FieldExpiresAt, vs...))
}
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
func ExpiresAtGT(v time.Time) predicate.Account {
return predicate.Account(sql.FieldGT(FieldExpiresAt, v))
}
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
func ExpiresAtGTE(v time.Time) predicate.Account {
return predicate.Account(sql.FieldGTE(FieldExpiresAt, v))
}
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
func ExpiresAtLT(v time.Time) predicate.Account {
return predicate.Account(sql.FieldLT(FieldExpiresAt, v))
}
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
func ExpiresAtLTE(v time.Time) predicate.Account {
return predicate.Account(sql.FieldLTE(FieldExpiresAt, v))
}
// ExpiresAtIsNil applies the IsNil predicate on the "expires_at" field.
func ExpiresAtIsNil() predicate.Account {
return predicate.Account(sql.FieldIsNull(FieldExpiresAt))
}
// ExpiresAtNotNil applies the NotNil predicate on the "expires_at" field.
func ExpiresAtNotNil() predicate.Account {
return predicate.Account(sql.FieldNotNull(FieldExpiresAt))
}
// AutoPauseOnExpiredEQ applies the EQ predicate on the "auto_pause_on_expired" field.
func AutoPauseOnExpiredEQ(v bool) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldAutoPauseOnExpired, v))
}
// AutoPauseOnExpiredNEQ applies the NEQ predicate on the "auto_pause_on_expired" field.
func AutoPauseOnExpiredNEQ(v bool) predicate.Account {
return predicate.Account(sql.FieldNEQ(FieldAutoPauseOnExpired, v))
}
// SchedulableEQ applies the EQ predicate on the "schedulable" field. // SchedulableEQ applies the EQ predicate on the "schedulable" field.
func SchedulableEQ(v bool) predicate.Account { func SchedulableEQ(v bool) predicate.Account {
return predicate.Account(sql.FieldEQ(FieldSchedulable, v)) return predicate.Account(sql.FieldEQ(FieldSchedulable, v))
......
...@@ -195,6 +195,34 @@ func (_c *AccountCreate) SetNillableLastUsedAt(v *time.Time) *AccountCreate { ...@@ -195,6 +195,34 @@ func (_c *AccountCreate) SetNillableLastUsedAt(v *time.Time) *AccountCreate {
return _c return _c
} }
// SetExpiresAt sets the "expires_at" field.
func (_c *AccountCreate) SetExpiresAt(v time.Time) *AccountCreate {
_c.mutation.SetExpiresAt(v)
return _c
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_c *AccountCreate) SetNillableExpiresAt(v *time.Time) *AccountCreate {
if v != nil {
_c.SetExpiresAt(*v)
}
return _c
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (_c *AccountCreate) SetAutoPauseOnExpired(v bool) *AccountCreate {
_c.mutation.SetAutoPauseOnExpired(v)
return _c
}
// SetNillableAutoPauseOnExpired sets the "auto_pause_on_expired" field if the given value is not nil.
func (_c *AccountCreate) SetNillableAutoPauseOnExpired(v *bool) *AccountCreate {
if v != nil {
_c.SetAutoPauseOnExpired(*v)
}
return _c
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (_c *AccountCreate) SetSchedulable(v bool) *AccountCreate { func (_c *AccountCreate) SetSchedulable(v bool) *AccountCreate {
_c.mutation.SetSchedulable(v) _c.mutation.SetSchedulable(v)
...@@ -405,6 +433,10 @@ func (_c *AccountCreate) defaults() error { ...@@ -405,6 +433,10 @@ func (_c *AccountCreate) defaults() error {
v := account.DefaultStatus v := account.DefaultStatus
_c.mutation.SetStatus(v) _c.mutation.SetStatus(v)
} }
if _, ok := _c.mutation.AutoPauseOnExpired(); !ok {
v := account.DefaultAutoPauseOnExpired
_c.mutation.SetAutoPauseOnExpired(v)
}
if _, ok := _c.mutation.Schedulable(); !ok { if _, ok := _c.mutation.Schedulable(); !ok {
v := account.DefaultSchedulable v := account.DefaultSchedulable
_c.mutation.SetSchedulable(v) _c.mutation.SetSchedulable(v)
...@@ -464,6 +496,9 @@ func (_c *AccountCreate) check() error { ...@@ -464,6 +496,9 @@ func (_c *AccountCreate) check() error {
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Account.status": %w`, err)} return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Account.status": %w`, err)}
} }
} }
if _, ok := _c.mutation.AutoPauseOnExpired(); !ok {
return &ValidationError{Name: "auto_pause_on_expired", err: errors.New(`ent: missing required field "Account.auto_pause_on_expired"`)}
}
if _, ok := _c.mutation.Schedulable(); !ok { if _, ok := _c.mutation.Schedulable(); !ok {
return &ValidationError{Name: "schedulable", err: errors.New(`ent: missing required field "Account.schedulable"`)} return &ValidationError{Name: "schedulable", err: errors.New(`ent: missing required field "Account.schedulable"`)}
} }
...@@ -555,6 +590,14 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) { ...@@ -555,6 +590,14 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
_spec.SetField(account.FieldLastUsedAt, field.TypeTime, value) _spec.SetField(account.FieldLastUsedAt, field.TypeTime, value)
_node.LastUsedAt = &value _node.LastUsedAt = &value
} }
if value, ok := _c.mutation.ExpiresAt(); ok {
_spec.SetField(account.FieldExpiresAt, field.TypeTime, value)
_node.ExpiresAt = &value
}
if value, ok := _c.mutation.AutoPauseOnExpired(); ok {
_spec.SetField(account.FieldAutoPauseOnExpired, field.TypeBool, value)
_node.AutoPauseOnExpired = value
}
if value, ok := _c.mutation.Schedulable(); ok { if value, ok := _c.mutation.Schedulable(); ok {
_spec.SetField(account.FieldSchedulable, field.TypeBool, value) _spec.SetField(account.FieldSchedulable, field.TypeBool, value)
_node.Schedulable = value _node.Schedulable = value
...@@ -898,6 +941,36 @@ func (u *AccountUpsert) ClearLastUsedAt() *AccountUpsert { ...@@ -898,6 +941,36 @@ func (u *AccountUpsert) ClearLastUsedAt() *AccountUpsert {
return u return u
} }
// SetExpiresAt sets the "expires_at" field.
func (u *AccountUpsert) SetExpiresAt(v time.Time) *AccountUpsert {
u.Set(account.FieldExpiresAt, v)
return u
}
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
func (u *AccountUpsert) UpdateExpiresAt() *AccountUpsert {
u.SetExcluded(account.FieldExpiresAt)
return u
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (u *AccountUpsert) ClearExpiresAt() *AccountUpsert {
u.SetNull(account.FieldExpiresAt)
return u
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (u *AccountUpsert) SetAutoPauseOnExpired(v bool) *AccountUpsert {
u.Set(account.FieldAutoPauseOnExpired, v)
return u
}
// UpdateAutoPauseOnExpired sets the "auto_pause_on_expired" field to the value that was provided on create.
func (u *AccountUpsert) UpdateAutoPauseOnExpired() *AccountUpsert {
u.SetExcluded(account.FieldAutoPauseOnExpired)
return u
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (u *AccountUpsert) SetSchedulable(v bool) *AccountUpsert { func (u *AccountUpsert) SetSchedulable(v bool) *AccountUpsert {
u.Set(account.FieldSchedulable, v) u.Set(account.FieldSchedulable, v)
...@@ -1308,6 +1381,41 @@ func (u *AccountUpsertOne) ClearLastUsedAt() *AccountUpsertOne { ...@@ -1308,6 +1381,41 @@ func (u *AccountUpsertOne) ClearLastUsedAt() *AccountUpsertOne {
}) })
} }
// SetExpiresAt sets the "expires_at" field.
func (u *AccountUpsertOne) SetExpiresAt(v time.Time) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetExpiresAt(v)
})
}
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateExpiresAt() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateExpiresAt()
})
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (u *AccountUpsertOne) ClearExpiresAt() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.ClearExpiresAt()
})
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (u *AccountUpsertOne) SetAutoPauseOnExpired(v bool) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.SetAutoPauseOnExpired(v)
})
}
// UpdateAutoPauseOnExpired sets the "auto_pause_on_expired" field to the value that was provided on create.
func (u *AccountUpsertOne) UpdateAutoPauseOnExpired() *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) {
s.UpdateAutoPauseOnExpired()
})
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (u *AccountUpsertOne) SetSchedulable(v bool) *AccountUpsertOne { func (u *AccountUpsertOne) SetSchedulable(v bool) *AccountUpsertOne {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
...@@ -1904,6 +2012,41 @@ func (u *AccountUpsertBulk) ClearLastUsedAt() *AccountUpsertBulk { ...@@ -1904,6 +2012,41 @@ func (u *AccountUpsertBulk) ClearLastUsedAt() *AccountUpsertBulk {
}) })
} }
// SetExpiresAt sets the "expires_at" field.
func (u *AccountUpsertBulk) SetExpiresAt(v time.Time) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetExpiresAt(v)
})
}
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateExpiresAt() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateExpiresAt()
})
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (u *AccountUpsertBulk) ClearExpiresAt() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.ClearExpiresAt()
})
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (u *AccountUpsertBulk) SetAutoPauseOnExpired(v bool) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.SetAutoPauseOnExpired(v)
})
}
// UpdateAutoPauseOnExpired sets the "auto_pause_on_expired" field to the value that was provided on create.
func (u *AccountUpsertBulk) UpdateAutoPauseOnExpired() *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) {
s.UpdateAutoPauseOnExpired()
})
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (u *AccountUpsertBulk) SetSchedulable(v bool) *AccountUpsertBulk { func (u *AccountUpsertBulk) SetSchedulable(v bool) *AccountUpsertBulk {
return u.Update(func(s *AccountUpsert) { return u.Update(func(s *AccountUpsert) {
......
...@@ -247,6 +247,40 @@ func (_u *AccountUpdate) ClearLastUsedAt() *AccountUpdate { ...@@ -247,6 +247,40 @@ func (_u *AccountUpdate) ClearLastUsedAt() *AccountUpdate {
return _u return _u
} }
// SetExpiresAt sets the "expires_at" field.
func (_u *AccountUpdate) SetExpiresAt(v time.Time) *AccountUpdate {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableExpiresAt(v *time.Time) *AccountUpdate {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (_u *AccountUpdate) ClearExpiresAt() *AccountUpdate {
_u.mutation.ClearExpiresAt()
return _u
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (_u *AccountUpdate) SetAutoPauseOnExpired(v bool) *AccountUpdate {
_u.mutation.SetAutoPauseOnExpired(v)
return _u
}
// SetNillableAutoPauseOnExpired sets the "auto_pause_on_expired" field if the given value is not nil.
func (_u *AccountUpdate) SetNillableAutoPauseOnExpired(v *bool) *AccountUpdate {
if v != nil {
_u.SetAutoPauseOnExpired(*v)
}
return _u
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (_u *AccountUpdate) SetSchedulable(v bool) *AccountUpdate { func (_u *AccountUpdate) SetSchedulable(v bool) *AccountUpdate {
_u.mutation.SetSchedulable(v) _u.mutation.SetSchedulable(v)
...@@ -610,6 +644,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) { ...@@ -610,6 +644,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if _u.mutation.LastUsedAtCleared() { if _u.mutation.LastUsedAtCleared() {
_spec.ClearField(account.FieldLastUsedAt, field.TypeTime) _spec.ClearField(account.FieldLastUsedAt, field.TypeTime)
} }
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(account.FieldExpiresAt, field.TypeTime, value)
}
if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(account.FieldExpiresAt, field.TypeTime)
}
if value, ok := _u.mutation.AutoPauseOnExpired(); ok {
_spec.SetField(account.FieldAutoPauseOnExpired, field.TypeBool, value)
}
if value, ok := _u.mutation.Schedulable(); ok { if value, ok := _u.mutation.Schedulable(); ok {
_spec.SetField(account.FieldSchedulable, field.TypeBool, value) _spec.SetField(account.FieldSchedulable, field.TypeBool, value)
} }
...@@ -1016,6 +1059,40 @@ func (_u *AccountUpdateOne) ClearLastUsedAt() *AccountUpdateOne { ...@@ -1016,6 +1059,40 @@ func (_u *AccountUpdateOne) ClearLastUsedAt() *AccountUpdateOne {
return _u return _u
} }
// SetExpiresAt sets the "expires_at" field.
func (_u *AccountUpdateOne) SetExpiresAt(v time.Time) *AccountUpdateOne {
_u.mutation.SetExpiresAt(v)
return _u
}
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableExpiresAt(v *time.Time) *AccountUpdateOne {
if v != nil {
_u.SetExpiresAt(*v)
}
return _u
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (_u *AccountUpdateOne) ClearExpiresAt() *AccountUpdateOne {
_u.mutation.ClearExpiresAt()
return _u
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (_u *AccountUpdateOne) SetAutoPauseOnExpired(v bool) *AccountUpdateOne {
_u.mutation.SetAutoPauseOnExpired(v)
return _u
}
// SetNillableAutoPauseOnExpired sets the "auto_pause_on_expired" field if the given value is not nil.
func (_u *AccountUpdateOne) SetNillableAutoPauseOnExpired(v *bool) *AccountUpdateOne {
if v != nil {
_u.SetAutoPauseOnExpired(*v)
}
return _u
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (_u *AccountUpdateOne) SetSchedulable(v bool) *AccountUpdateOne { func (_u *AccountUpdateOne) SetSchedulable(v bool) *AccountUpdateOne {
_u.mutation.SetSchedulable(v) _u.mutation.SetSchedulable(v)
...@@ -1409,6 +1486,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er ...@@ -1409,6 +1486,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
if _u.mutation.LastUsedAtCleared() { if _u.mutation.LastUsedAtCleared() {
_spec.ClearField(account.FieldLastUsedAt, field.TypeTime) _spec.ClearField(account.FieldLastUsedAt, field.TypeTime)
} }
if value, ok := _u.mutation.ExpiresAt(); ok {
_spec.SetField(account.FieldExpiresAt, field.TypeTime, value)
}
if _u.mutation.ExpiresAtCleared() {
_spec.ClearField(account.FieldExpiresAt, field.TypeTime)
}
if value, ok := _u.mutation.AutoPauseOnExpired(); ok {
_spec.SetField(account.FieldAutoPauseOnExpired, field.TypeBool, value)
}
if value, ok := _u.mutation.Schedulable(); ok { if value, ok := _u.mutation.Schedulable(); ok {
_spec.SetField(account.FieldSchedulable, field.TypeBool, value) _spec.SetField(account.FieldSchedulable, field.TypeBool, value)
} }
......
...@@ -80,6 +80,8 @@ var ( ...@@ -80,6 +80,8 @@ var (
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"}, {Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}}, {Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "last_used_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, {Name: "last_used_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "expires_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "auto_pause_on_expired", Type: field.TypeBool, Default: true},
{Name: "schedulable", Type: field.TypeBool, Default: true}, {Name: "schedulable", Type: field.TypeBool, Default: true},
{Name: "rate_limited_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, {Name: "rate_limited_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "rate_limit_reset_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}}, {Name: "rate_limit_reset_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
...@@ -97,7 +99,7 @@ var ( ...@@ -97,7 +99,7 @@ var (
ForeignKeys: []*schema.ForeignKey{ ForeignKeys: []*schema.ForeignKey{
{ {
Symbol: "accounts_proxies_proxy", Symbol: "accounts_proxies_proxy",
Columns: []*schema.Column{AccountsColumns[22]}, Columns: []*schema.Column{AccountsColumns[24]},
RefColumns: []*schema.Column{ProxiesColumns[0]}, RefColumns: []*schema.Column{ProxiesColumns[0]},
OnDelete: schema.SetNull, OnDelete: schema.SetNull,
}, },
...@@ -121,7 +123,7 @@ var ( ...@@ -121,7 +123,7 @@ var (
{ {
Name: "account_proxy_id", Name: "account_proxy_id",
Unique: false, Unique: false,
Columns: []*schema.Column{AccountsColumns[22]}, Columns: []*schema.Column{AccountsColumns[24]},
}, },
{ {
Name: "account_priority", Name: "account_priority",
...@@ -136,22 +138,22 @@ var ( ...@@ -136,22 +138,22 @@ var (
{ {
Name: "account_schedulable", Name: "account_schedulable",
Unique: false, Unique: false,
Columns: []*schema.Column{AccountsColumns[15]}, Columns: []*schema.Column{AccountsColumns[17]},
}, },
{ {
Name: "account_rate_limited_at", Name: "account_rate_limited_at",
Unique: false, Unique: false,
Columns: []*schema.Column{AccountsColumns[16]}, Columns: []*schema.Column{AccountsColumns[18]},
}, },
{ {
Name: "account_rate_limit_reset_at", Name: "account_rate_limit_reset_at",
Unique: false, Unique: false,
Columns: []*schema.Column{AccountsColumns[17]}, Columns: []*schema.Column{AccountsColumns[19]},
}, },
{ {
Name: "account_overload_until", Name: "account_overload_until",
Unique: false, Unique: false,
Columns: []*schema.Column{AccountsColumns[18]}, Columns: []*schema.Column{AccountsColumns[20]},
}, },
{ {
Name: "account_deleted_at", Name: "account_deleted_at",
......
...@@ -1006,6 +1006,8 @@ type AccountMutation struct { ...@@ -1006,6 +1006,8 @@ type AccountMutation struct {
status *string status *string
error_message *string error_message *string
last_used_at *time.Time last_used_at *time.Time
expires_at *time.Time
auto_pause_on_expired *bool
schedulable *bool schedulable *bool
rate_limited_at *time.Time rate_limited_at *time.Time
rate_limit_reset_at *time.Time rate_limit_reset_at *time.Time
...@@ -1770,6 +1772,91 @@ func (m *AccountMutation) ResetLastUsedAt() { ...@@ -1770,6 +1772,91 @@ func (m *AccountMutation) ResetLastUsedAt() {
delete(m.clearedFields, account.FieldLastUsedAt) delete(m.clearedFields, account.FieldLastUsedAt)
} }
// SetExpiresAt sets the "expires_at" field.
func (m *AccountMutation) SetExpiresAt(t time.Time) {
m.expires_at = &t
}
// ExpiresAt returns the value of the "expires_at" field in the mutation.
func (m *AccountMutation) ExpiresAt() (r time.Time, exists bool) {
v := m.expires_at
if v == nil {
return
}
return *v, true
}
// OldExpiresAt returns the old "expires_at" field's value of the Account entity.
// If the Account 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 *AccountMutation) OldExpiresAt(ctx context.Context) (v *time.Time, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldExpiresAt is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldExpiresAt requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldExpiresAt: %w", err)
}
return oldValue.ExpiresAt, nil
}
// ClearExpiresAt clears the value of the "expires_at" field.
func (m *AccountMutation) ClearExpiresAt() {
m.expires_at = nil
m.clearedFields[account.FieldExpiresAt] = struct{}{}
}
// ExpiresAtCleared returns if the "expires_at" field was cleared in this mutation.
func (m *AccountMutation) ExpiresAtCleared() bool {
_, ok := m.clearedFields[account.FieldExpiresAt]
return ok
}
// ResetExpiresAt resets all changes to the "expires_at" field.
func (m *AccountMutation) ResetExpiresAt() {
m.expires_at = nil
delete(m.clearedFields, account.FieldExpiresAt)
}
// SetAutoPauseOnExpired sets the "auto_pause_on_expired" field.
func (m *AccountMutation) SetAutoPauseOnExpired(b bool) {
m.auto_pause_on_expired = &b
}
// AutoPauseOnExpired returns the value of the "auto_pause_on_expired" field in the mutation.
func (m *AccountMutation) AutoPauseOnExpired() (r bool, exists bool) {
v := m.auto_pause_on_expired
if v == nil {
return
}
return *v, true
}
// OldAutoPauseOnExpired returns the old "auto_pause_on_expired" field's value of the Account entity.
// If the Account 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 *AccountMutation) OldAutoPauseOnExpired(ctx context.Context) (v bool, err error) {
if !m.op.Is(OpUpdateOne) {
return v, errors.New("OldAutoPauseOnExpired is only allowed on UpdateOne operations")
}
if m.id == nil || m.oldValue == nil {
return v, errors.New("OldAutoPauseOnExpired requires an ID field in the mutation")
}
oldValue, err := m.oldValue(ctx)
if err != nil {
return v, fmt.Errorf("querying old value for OldAutoPauseOnExpired: %w", err)
}
return oldValue.AutoPauseOnExpired, nil
}
// ResetAutoPauseOnExpired resets all changes to the "auto_pause_on_expired" field.
func (m *AccountMutation) ResetAutoPauseOnExpired() {
m.auto_pause_on_expired = nil
}
// SetSchedulable sets the "schedulable" field. // SetSchedulable sets the "schedulable" field.
func (m *AccountMutation) SetSchedulable(b bool) { func (m *AccountMutation) SetSchedulable(b bool) {
m.schedulable = &b m.schedulable = &b
...@@ -2269,7 +2356,7 @@ func (m *AccountMutation) Type() string { ...@@ -2269,7 +2356,7 @@ func (m *AccountMutation) 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 *AccountMutation) Fields() []string { func (m *AccountMutation) Fields() []string {
fields := make([]string, 0, 22) fields := make([]string, 0, 24)
if m.created_at != nil { if m.created_at != nil {
fields = append(fields, account.FieldCreatedAt) fields = append(fields, account.FieldCreatedAt)
} }
...@@ -2315,6 +2402,12 @@ func (m *AccountMutation) Fields() []string { ...@@ -2315,6 +2402,12 @@ func (m *AccountMutation) Fields() []string {
if m.last_used_at != nil { if m.last_used_at != nil {
fields = append(fields, account.FieldLastUsedAt) fields = append(fields, account.FieldLastUsedAt)
} }
if m.expires_at != nil {
fields = append(fields, account.FieldExpiresAt)
}
if m.auto_pause_on_expired != nil {
fields = append(fields, account.FieldAutoPauseOnExpired)
}
if m.schedulable != nil { if m.schedulable != nil {
fields = append(fields, account.FieldSchedulable) fields = append(fields, account.FieldSchedulable)
} }
...@@ -2374,6 +2467,10 @@ func (m *AccountMutation) Field(name string) (ent.Value, bool) { ...@@ -2374,6 +2467,10 @@ func (m *AccountMutation) Field(name string) (ent.Value, bool) {
return m.ErrorMessage() return m.ErrorMessage()
case account.FieldLastUsedAt: case account.FieldLastUsedAt:
return m.LastUsedAt() return m.LastUsedAt()
case account.FieldExpiresAt:
return m.ExpiresAt()
case account.FieldAutoPauseOnExpired:
return m.AutoPauseOnExpired()
case account.FieldSchedulable: case account.FieldSchedulable:
return m.Schedulable() return m.Schedulable()
case account.FieldRateLimitedAt: case account.FieldRateLimitedAt:
...@@ -2427,6 +2524,10 @@ func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value, ...@@ -2427,6 +2524,10 @@ func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value,
return m.OldErrorMessage(ctx) return m.OldErrorMessage(ctx)
case account.FieldLastUsedAt: case account.FieldLastUsedAt:
return m.OldLastUsedAt(ctx) return m.OldLastUsedAt(ctx)
case account.FieldExpiresAt:
return m.OldExpiresAt(ctx)
case account.FieldAutoPauseOnExpired:
return m.OldAutoPauseOnExpired(ctx)
case account.FieldSchedulable: case account.FieldSchedulable:
return m.OldSchedulable(ctx) return m.OldSchedulable(ctx)
case account.FieldRateLimitedAt: case account.FieldRateLimitedAt:
...@@ -2555,6 +2656,20 @@ func (m *AccountMutation) SetField(name string, value ent.Value) error { ...@@ -2555,6 +2656,20 @@ func (m *AccountMutation) SetField(name string, value ent.Value) error {
} }
m.SetLastUsedAt(v) m.SetLastUsedAt(v)
return nil return nil
case account.FieldExpiresAt:
v, ok := value.(time.Time)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetExpiresAt(v)
return nil
case account.FieldAutoPauseOnExpired:
v, ok := value.(bool)
if !ok {
return fmt.Errorf("unexpected type %T for field %s", value, name)
}
m.SetAutoPauseOnExpired(v)
return nil
case account.FieldSchedulable: case account.FieldSchedulable:
v, ok := value.(bool) v, ok := value.(bool)
if !ok { if !ok {
...@@ -2676,6 +2791,9 @@ func (m *AccountMutation) ClearedFields() []string { ...@@ -2676,6 +2791,9 @@ func (m *AccountMutation) ClearedFields() []string {
if m.FieldCleared(account.FieldLastUsedAt) { if m.FieldCleared(account.FieldLastUsedAt) {
fields = append(fields, account.FieldLastUsedAt) fields = append(fields, account.FieldLastUsedAt)
} }
if m.FieldCleared(account.FieldExpiresAt) {
fields = append(fields, account.FieldExpiresAt)
}
if m.FieldCleared(account.FieldRateLimitedAt) { if m.FieldCleared(account.FieldRateLimitedAt) {
fields = append(fields, account.FieldRateLimitedAt) fields = append(fields, account.FieldRateLimitedAt)
} }
...@@ -2723,6 +2841,9 @@ func (m *AccountMutation) ClearField(name string) error { ...@@ -2723,6 +2841,9 @@ func (m *AccountMutation) ClearField(name string) error {
case account.FieldLastUsedAt: case account.FieldLastUsedAt:
m.ClearLastUsedAt() m.ClearLastUsedAt()
return nil return nil
case account.FieldExpiresAt:
m.ClearExpiresAt()
return nil
case account.FieldRateLimitedAt: case account.FieldRateLimitedAt:
m.ClearRateLimitedAt() m.ClearRateLimitedAt()
return nil return nil
...@@ -2794,6 +2915,12 @@ func (m *AccountMutation) ResetField(name string) error { ...@@ -2794,6 +2915,12 @@ func (m *AccountMutation) ResetField(name string) error {
case account.FieldLastUsedAt: case account.FieldLastUsedAt:
m.ResetLastUsedAt() m.ResetLastUsedAt()
return nil return nil
case account.FieldExpiresAt:
m.ResetExpiresAt()
return nil
case account.FieldAutoPauseOnExpired:
m.ResetAutoPauseOnExpired()
return nil
case account.FieldSchedulable: case account.FieldSchedulable:
m.ResetSchedulable() m.ResetSchedulable()
return nil return nil
......
...@@ -181,12 +181,16 @@ func init() { ...@@ -181,12 +181,16 @@ func init() {
account.DefaultStatus = accountDescStatus.Default.(string) account.DefaultStatus = accountDescStatus.Default.(string)
// account.StatusValidator is a validator for the "status" field. It is called by the builders before save. // account.StatusValidator is a validator for the "status" field. It is called by the builders before save.
account.StatusValidator = accountDescStatus.Validators[0].(func(string) error) account.StatusValidator = accountDescStatus.Validators[0].(func(string) error)
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
accountDescAutoPauseOnExpired := accountFields[13].Descriptor()
// account.DefaultAutoPauseOnExpired holds the default value on creation for the auto_pause_on_expired field.
account.DefaultAutoPauseOnExpired = accountDescAutoPauseOnExpired.Default.(bool)
// accountDescSchedulable is the schema descriptor for schedulable field. // accountDescSchedulable is the schema descriptor for schedulable field.
accountDescSchedulable := accountFields[12].Descriptor() accountDescSchedulable := accountFields[14].Descriptor()
// account.DefaultSchedulable holds the default value on creation for the schedulable field. // account.DefaultSchedulable holds the default value on creation for the schedulable field.
account.DefaultSchedulable = accountDescSchedulable.Default.(bool) account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field. // accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
accountDescSessionWindowStatus := accountFields[18].Descriptor() accountDescSessionWindowStatus := accountFields[20].Descriptor()
// account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save. // account.SessionWindowStatusValidator is a validator for the "session_window_status" field. It is called by the builders before save.
account.SessionWindowStatusValidator = accountDescSessionWindowStatus.Validators[0].(func(string) error) account.SessionWindowStatusValidator = accountDescSessionWindowStatus.Validators[0].(func(string) error)
accountgroupFields := schema.AccountGroup{}.Fields() accountgroupFields := schema.AccountGroup{}.Fields()
......
...@@ -118,6 +118,16 @@ func (Account) Fields() []ent.Field { ...@@ -118,6 +118,16 @@ func (Account) Fields() []ent.Field {
Optional(). Optional().
Nillable(). Nillable().
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}), SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
// expires_at: 账户过期时间(可为空)
field.Time("expires_at").
Optional().
Nillable().
Comment("Account expiration time (NULL means no expiration).").
SchemaType(map[string]string{dialect.Postgres: "timestamptz"}),
// auto_pause_on_expired: 过期后自动暂停调度
field.Bool("auto_pause_on_expired").
Default(true).
Comment("Auto pause scheduling when account expires."),
// ========== 调度和速率限制相关字段 ========== // ========== 调度和速率限制相关字段 ==========
// 这些字段在 migrations/005_schema_parity.sql 中添加 // 这些字段在 migrations/005_schema_parity.sql 中添加
......
...@@ -85,6 +85,8 @@ type CreateAccountRequest struct { ...@@ -85,6 +85,8 @@ type CreateAccountRequest struct {
Concurrency int `json:"concurrency"` Concurrency int `json:"concurrency"`
Priority int `json:"priority"` Priority int `json:"priority"`
GroupIDs []int64 `json:"group_ids"` GroupIDs []int64 `json:"group_ids"`
ExpiresAt *int64 `json:"expires_at"`
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
ConfirmMixedChannelRisk *bool `json:"confirm_mixed_channel_risk"` // 用户确认混合渠道风险 ConfirmMixedChannelRisk *bool `json:"confirm_mixed_channel_risk"` // 用户确认混合渠道风险
} }
...@@ -101,6 +103,8 @@ type UpdateAccountRequest struct { ...@@ -101,6 +103,8 @@ type UpdateAccountRequest struct {
Priority *int `json:"priority"` Priority *int `json:"priority"`
Status string `json:"status" binding:"omitempty,oneof=active inactive"` Status string `json:"status" binding:"omitempty,oneof=active inactive"`
GroupIDs *[]int64 `json:"group_ids"` GroupIDs *[]int64 `json:"group_ids"`
ExpiresAt *int64 `json:"expires_at"`
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
ConfirmMixedChannelRisk *bool `json:"confirm_mixed_channel_risk"` // 用户确认混合渠道风险 ConfirmMixedChannelRisk *bool `json:"confirm_mixed_channel_risk"` // 用户确认混合渠道风险
} }
...@@ -204,6 +208,8 @@ func (h *AccountHandler) Create(c *gin.Context) { ...@@ -204,6 +208,8 @@ func (h *AccountHandler) Create(c *gin.Context) {
Concurrency: req.Concurrency, Concurrency: req.Concurrency,
Priority: req.Priority, Priority: req.Priority,
GroupIDs: req.GroupIDs, GroupIDs: req.GroupIDs,
ExpiresAt: req.ExpiresAt,
AutoPauseOnExpired: req.AutoPauseOnExpired,
SkipMixedChannelCheck: skipCheck, SkipMixedChannelCheck: skipCheck,
}) })
if err != nil { if err != nil {
...@@ -261,6 +267,8 @@ func (h *AccountHandler) Update(c *gin.Context) { ...@@ -261,6 +267,8 @@ func (h *AccountHandler) Update(c *gin.Context) {
Priority: req.Priority, // 指针类型,nil 表示未提供 Priority: req.Priority, // 指针类型,nil 表示未提供
Status: req.Status, Status: req.Status,
GroupIDs: req.GroupIDs, GroupIDs: req.GroupIDs,
ExpiresAt: req.ExpiresAt,
AutoPauseOnExpired: req.AutoPauseOnExpired,
SkipMixedChannelCheck: skipCheck, SkipMixedChannelCheck: skipCheck,
}) })
if err != nil { if err != nil {
......
// Package dto provides data transfer objects for HTTP handlers. // Package dto provides data transfer objects for HTTP handlers.
package dto package dto
import "github.com/Wei-Shaw/sub2api/internal/service" import (
"time"
"github.com/Wei-Shaw/sub2api/internal/service"
)
func UserFromServiceShallow(u *service.User) *User { func UserFromServiceShallow(u *service.User) *User {
if u == nil { if u == nil {
...@@ -120,6 +124,8 @@ func AccountFromServiceShallow(a *service.Account) *Account { ...@@ -120,6 +124,8 @@ func AccountFromServiceShallow(a *service.Account) *Account {
Status: a.Status, Status: a.Status,
ErrorMessage: a.ErrorMessage, ErrorMessage: a.ErrorMessage,
LastUsedAt: a.LastUsedAt, LastUsedAt: a.LastUsedAt,
ExpiresAt: timeToUnixSeconds(a.ExpiresAt),
AutoPauseOnExpired: a.AutoPauseOnExpired,
CreatedAt: a.CreatedAt, CreatedAt: a.CreatedAt,
UpdatedAt: a.UpdatedAt, UpdatedAt: a.UpdatedAt,
Schedulable: a.Schedulable, Schedulable: a.Schedulable,
...@@ -157,6 +163,14 @@ func AccountFromService(a *service.Account) *Account { ...@@ -157,6 +163,14 @@ func AccountFromService(a *service.Account) *Account {
return out return out
} }
func timeToUnixSeconds(value *time.Time) *int64 {
if value == nil {
return nil
}
ts := value.Unix()
return &ts
}
func AccountGroupFromService(ag *service.AccountGroup) *AccountGroup { func AccountGroupFromService(ag *service.AccountGroup) *AccountGroup {
if ag == nil { if ag == nil {
return nil return nil
......
...@@ -60,21 +60,23 @@ type Group struct { ...@@ -60,21 +60,23 @@ type Group struct {
} }
type Account struct { type Account struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Notes *string `json:"notes"` Notes *string `json:"notes"`
Platform string `json:"platform"` Platform string `json:"platform"`
Type string `json:"type"` Type string `json:"type"`
Credentials map[string]any `json:"credentials"` Credentials map[string]any `json:"credentials"`
Extra map[string]any `json:"extra"` Extra map[string]any `json:"extra"`
ProxyID *int64 `json:"proxy_id"` ProxyID *int64 `json:"proxy_id"`
Concurrency int `json:"concurrency"` Concurrency int `json:"concurrency"`
Priority int `json:"priority"` Priority int `json:"priority"`
Status string `json:"status"` Status string `json:"status"`
ErrorMessage string `json:"error_message"` ErrorMessage string `json:"error_message"`
LastUsedAt *time.Time `json:"last_used_at"` LastUsedAt *time.Time `json:"last_used_at"`
CreatedAt time.Time `json:"created_at"` ExpiresAt *int64 `json:"expires_at"`
UpdatedAt time.Time `json:"updated_at"` AutoPauseOnExpired bool `json:"auto_pause_on_expired"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Schedulable bool `json:"schedulable"` Schedulable bool `json:"schedulable"`
......
...@@ -76,7 +76,8 @@ func (r *accountRepository) Create(ctx context.Context, account *service.Account ...@@ -76,7 +76,8 @@ func (r *accountRepository) Create(ctx context.Context, account *service.Account
SetPriority(account.Priority). SetPriority(account.Priority).
SetStatus(account.Status). SetStatus(account.Status).
SetErrorMessage(account.ErrorMessage). SetErrorMessage(account.ErrorMessage).
SetSchedulable(account.Schedulable) SetSchedulable(account.Schedulable).
SetAutoPauseOnExpired(account.AutoPauseOnExpired)
if account.ProxyID != nil { if account.ProxyID != nil {
builder.SetProxyID(*account.ProxyID) builder.SetProxyID(*account.ProxyID)
...@@ -84,6 +85,9 @@ func (r *accountRepository) Create(ctx context.Context, account *service.Account ...@@ -84,6 +85,9 @@ func (r *accountRepository) Create(ctx context.Context, account *service.Account
if account.LastUsedAt != nil { if account.LastUsedAt != nil {
builder.SetLastUsedAt(*account.LastUsedAt) builder.SetLastUsedAt(*account.LastUsedAt)
} }
if account.ExpiresAt != nil {
builder.SetExpiresAt(*account.ExpiresAt)
}
if account.RateLimitedAt != nil { if account.RateLimitedAt != nil {
builder.SetRateLimitedAt(*account.RateLimitedAt) builder.SetRateLimitedAt(*account.RateLimitedAt)
} }
...@@ -280,7 +284,8 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account ...@@ -280,7 +284,8 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account
SetPriority(account.Priority). SetPriority(account.Priority).
SetStatus(account.Status). SetStatus(account.Status).
SetErrorMessage(account.ErrorMessage). SetErrorMessage(account.ErrorMessage).
SetSchedulable(account.Schedulable) SetSchedulable(account.Schedulable).
SetAutoPauseOnExpired(account.AutoPauseOnExpired)
if account.ProxyID != nil { if account.ProxyID != nil {
builder.SetProxyID(*account.ProxyID) builder.SetProxyID(*account.ProxyID)
...@@ -292,6 +297,11 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account ...@@ -292,6 +297,11 @@ func (r *accountRepository) Update(ctx context.Context, account *service.Account
} else { } else {
builder.ClearLastUsedAt() builder.ClearLastUsedAt()
} }
if account.ExpiresAt != nil {
builder.SetExpiresAt(*account.ExpiresAt)
} else {
builder.ClearExpiresAt()
}
if account.RateLimitedAt != nil { if account.RateLimitedAt != nil {
builder.SetRateLimitedAt(*account.RateLimitedAt) builder.SetRateLimitedAt(*account.RateLimitedAt)
} else { } else {
...@@ -570,6 +580,7 @@ func (r *accountRepository) ListSchedulable(ctx context.Context) ([]service.Acco ...@@ -570,6 +580,7 @@ func (r *accountRepository) ListSchedulable(ctx context.Context) ([]service.Acco
dbaccount.StatusEQ(service.StatusActive), dbaccount.StatusEQ(service.StatusActive),
dbaccount.SchedulableEQ(true), dbaccount.SchedulableEQ(true),
tempUnschedulablePredicate(), tempUnschedulablePredicate(),
notExpiredPredicate(now),
dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)), dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)),
dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)), dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)),
). ).
...@@ -596,6 +607,7 @@ func (r *accountRepository) ListSchedulableByPlatform(ctx context.Context, platf ...@@ -596,6 +607,7 @@ func (r *accountRepository) ListSchedulableByPlatform(ctx context.Context, platf
dbaccount.StatusEQ(service.StatusActive), dbaccount.StatusEQ(service.StatusActive),
dbaccount.SchedulableEQ(true), dbaccount.SchedulableEQ(true),
tempUnschedulablePredicate(), tempUnschedulablePredicate(),
notExpiredPredicate(now),
dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)), dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)),
dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)), dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)),
). ).
...@@ -629,6 +641,7 @@ func (r *accountRepository) ListSchedulableByPlatforms(ctx context.Context, plat ...@@ -629,6 +641,7 @@ func (r *accountRepository) ListSchedulableByPlatforms(ctx context.Context, plat
dbaccount.StatusEQ(service.StatusActive), dbaccount.StatusEQ(service.StatusActive),
dbaccount.SchedulableEQ(true), dbaccount.SchedulableEQ(true),
tempUnschedulablePredicate(), tempUnschedulablePredicate(),
notExpiredPredicate(now),
dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)), dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)),
dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)), dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)),
). ).
...@@ -727,6 +740,27 @@ func (r *accountRepository) SetSchedulable(ctx context.Context, id int64, schedu ...@@ -727,6 +740,27 @@ func (r *accountRepository) SetSchedulable(ctx context.Context, id int64, schedu
return err return err
} }
func (r *accountRepository) AutoPauseExpiredAccounts(ctx context.Context, now time.Time) (int64, error) {
result, err := r.sql.ExecContext(ctx, `
UPDATE accounts
SET schedulable = FALSE,
updated_at = NOW()
WHERE deleted_at IS NULL
AND schedulable = TRUE
AND auto_pause_on_expired = TRUE
AND expires_at IS NOT NULL
AND expires_at <= $1
`, now)
if err != nil {
return 0, err
}
rows, err := result.RowsAffected()
if err != nil {
return 0, err
}
return rows, nil
}
func (r *accountRepository) UpdateExtra(ctx context.Context, id int64, updates map[string]any) error { func (r *accountRepository) UpdateExtra(ctx context.Context, id int64, updates map[string]any) error {
if len(updates) == 0 { if len(updates) == 0 {
return nil return nil
...@@ -861,6 +895,7 @@ func (r *accountRepository) queryAccountsByGroup(ctx context.Context, groupID in ...@@ -861,6 +895,7 @@ func (r *accountRepository) queryAccountsByGroup(ctx context.Context, groupID in
preds = append(preds, preds = append(preds,
dbaccount.SchedulableEQ(true), dbaccount.SchedulableEQ(true),
tempUnschedulablePredicate(), tempUnschedulablePredicate(),
notExpiredPredicate(now),
dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)), dbaccount.Or(dbaccount.OverloadUntilIsNil(), dbaccount.OverloadUntilLTE(now)),
dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)), dbaccount.Or(dbaccount.RateLimitResetAtIsNil(), dbaccount.RateLimitResetAtLTE(now)),
) )
...@@ -971,6 +1006,14 @@ func tempUnschedulablePredicate() dbpredicate.Account { ...@@ -971,6 +1006,14 @@ func tempUnschedulablePredicate() dbpredicate.Account {
}) })
} }
func notExpiredPredicate(now time.Time) dbpredicate.Account {
return dbaccount.Or(
dbaccount.ExpiresAtIsNil(),
dbaccount.ExpiresAtGT(now),
dbaccount.AutoPauseOnExpiredEQ(false),
)
}
func (r *accountRepository) loadTempUnschedStates(ctx context.Context, accountIDs []int64) (map[int64]tempUnschedSnapshot, error) { func (r *accountRepository) loadTempUnschedStates(ctx context.Context, accountIDs []int64) (map[int64]tempUnschedSnapshot, error) {
out := make(map[int64]tempUnschedSnapshot) out := make(map[int64]tempUnschedSnapshot)
if len(accountIDs) == 0 { if len(accountIDs) == 0 {
...@@ -1086,6 +1129,8 @@ func accountEntityToService(m *dbent.Account) *service.Account { ...@@ -1086,6 +1129,8 @@ func accountEntityToService(m *dbent.Account) *service.Account {
Status: m.Status, Status: m.Status,
ErrorMessage: derefString(m.ErrorMessage), ErrorMessage: derefString(m.ErrorMessage),
LastUsedAt: m.LastUsedAt, LastUsedAt: m.LastUsedAt,
ExpiresAt: m.ExpiresAt,
AutoPauseOnExpired: m.AutoPauseOnExpired,
CreatedAt: m.CreatedAt, CreatedAt: m.CreatedAt,
UpdatedAt: m.UpdatedAt, UpdatedAt: m.UpdatedAt,
Schedulable: m.Schedulable, Schedulable: m.Schedulable,
......
...@@ -9,21 +9,23 @@ import ( ...@@ -9,21 +9,23 @@ import (
) )
type Account struct { type Account struct {
ID int64 ID int64
Name string Name string
Notes *string Notes *string
Platform string Platform string
Type string Type string
Credentials map[string]any Credentials map[string]any
Extra map[string]any Extra map[string]any
ProxyID *int64 ProxyID *int64
Concurrency int Concurrency int
Priority int Priority int
Status string Status string
ErrorMessage string ErrorMessage string
LastUsedAt *time.Time LastUsedAt *time.Time
CreatedAt time.Time ExpiresAt *time.Time
UpdatedAt time.Time AutoPauseOnExpired bool
CreatedAt time.Time
UpdatedAt time.Time
Schedulable bool Schedulable bool
...@@ -60,6 +62,9 @@ func (a *Account) IsSchedulable() bool { ...@@ -60,6 +62,9 @@ func (a *Account) IsSchedulable() bool {
return false return false
} }
now := time.Now() now := time.Now()
if a.AutoPauseOnExpired && a.ExpiresAt != nil && !now.Before(*a.ExpiresAt) {
return false
}
if a.OverloadUntil != nil && now.Before(*a.OverloadUntil) { if a.OverloadUntil != nil && now.Before(*a.OverloadUntil) {
return false return false
} }
......
package service
import (
"context"
"log"
"sync"
"time"
)
// AccountExpiryService periodically pauses expired accounts when auto-pause is enabled.
type AccountExpiryService struct {
accountRepo AccountRepository
interval time.Duration
stopCh chan struct{}
stopOnce sync.Once
wg sync.WaitGroup
}
func NewAccountExpiryService(accountRepo AccountRepository, interval time.Duration) *AccountExpiryService {
return &AccountExpiryService{
accountRepo: accountRepo,
interval: interval,
stopCh: make(chan struct{}),
}
}
func (s *AccountExpiryService) Start() {
if s == nil || s.accountRepo == nil || s.interval <= 0 {
return
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
defer ticker.Stop()
s.runOnce()
for {
select {
case <-ticker.C:
s.runOnce()
case <-s.stopCh:
return
}
}
}()
}
func (s *AccountExpiryService) Stop() {
if s == nil {
return
}
s.stopOnce.Do(func() {
close(s.stopCh)
})
s.wg.Wait()
}
func (s *AccountExpiryService) runOnce() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
updated, err := s.accountRepo.AutoPauseExpiredAccounts(ctx, time.Now())
if err != nil {
log.Printf("[AccountExpiry] Auto pause expired accounts failed: %v", err)
return
}
if updated > 0 {
log.Printf("[AccountExpiry] Auto paused %d expired accounts", updated)
}
}
...@@ -38,6 +38,7 @@ type AccountRepository interface { ...@@ -38,6 +38,7 @@ type AccountRepository interface {
BatchUpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error BatchUpdateLastUsed(ctx context.Context, updates map[int64]time.Time) error
SetError(ctx context.Context, id int64, errorMsg string) error SetError(ctx context.Context, id int64, errorMsg string) error
SetSchedulable(ctx context.Context, id int64, schedulable bool) error SetSchedulable(ctx context.Context, id int64, schedulable bool) error
AutoPauseExpiredAccounts(ctx context.Context, now time.Time) (int64, error)
BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error
ListSchedulable(ctx context.Context) ([]Account, error) ListSchedulable(ctx context.Context) ([]Account, error)
...@@ -71,29 +72,33 @@ type AccountBulkUpdate struct { ...@@ -71,29 +72,33 @@ type AccountBulkUpdate struct {
// CreateAccountRequest 创建账号请求 // CreateAccountRequest 创建账号请求
type CreateAccountRequest struct { type CreateAccountRequest struct {
Name string `json:"name"` Name string `json:"name"`
Notes *string `json:"notes"` Notes *string `json:"notes"`
Platform string `json:"platform"` Platform string `json:"platform"`
Type string `json:"type"` Type string `json:"type"`
Credentials map[string]any `json:"credentials"` Credentials map[string]any `json:"credentials"`
Extra map[string]any `json:"extra"` Extra map[string]any `json:"extra"`
ProxyID *int64 `json:"proxy_id"` ProxyID *int64 `json:"proxy_id"`
Concurrency int `json:"concurrency"` Concurrency int `json:"concurrency"`
Priority int `json:"priority"` Priority int `json:"priority"`
GroupIDs []int64 `json:"group_ids"` GroupIDs []int64 `json:"group_ids"`
ExpiresAt *time.Time `json:"expires_at"`
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
} }
// UpdateAccountRequest 更新账号请求 // UpdateAccountRequest 更新账号请求
type UpdateAccountRequest struct { type UpdateAccountRequest struct {
Name *string `json:"name"` Name *string `json:"name"`
Notes *string `json:"notes"` Notes *string `json:"notes"`
Credentials *map[string]any `json:"credentials"` Credentials *map[string]any `json:"credentials"`
Extra *map[string]any `json:"extra"` Extra *map[string]any `json:"extra"`
ProxyID *int64 `json:"proxy_id"` ProxyID *int64 `json:"proxy_id"`
Concurrency *int `json:"concurrency"` Concurrency *int `json:"concurrency"`
Priority *int `json:"priority"` Priority *int `json:"priority"`
Status *string `json:"status"` Status *string `json:"status"`
GroupIDs *[]int64 `json:"group_ids"` GroupIDs *[]int64 `json:"group_ids"`
ExpiresAt *time.Time `json:"expires_at"`
AutoPauseOnExpired *bool `json:"auto_pause_on_expired"`
} }
// AccountService 账号管理服务 // AccountService 账号管理服务
...@@ -134,6 +139,12 @@ func (s *AccountService) Create(ctx context.Context, req CreateAccountRequest) ( ...@@ -134,6 +139,12 @@ func (s *AccountService) Create(ctx context.Context, req CreateAccountRequest) (
Concurrency: req.Concurrency, Concurrency: req.Concurrency,
Priority: req.Priority, Priority: req.Priority,
Status: StatusActive, Status: StatusActive,
ExpiresAt: req.ExpiresAt,
}
if req.AutoPauseOnExpired != nil {
account.AutoPauseOnExpired = *req.AutoPauseOnExpired
} else {
account.AutoPauseOnExpired = true
} }
if err := s.accountRepo.Create(ctx, account); err != nil { if err := s.accountRepo.Create(ctx, account); err != nil {
...@@ -224,6 +235,12 @@ func (s *AccountService) Update(ctx context.Context, id int64, req UpdateAccount ...@@ -224,6 +235,12 @@ func (s *AccountService) Update(ctx context.Context, id int64, req UpdateAccount
if req.Status != nil { if req.Status != nil {
account.Status = *req.Status account.Status = *req.Status
} }
if req.ExpiresAt != nil {
account.ExpiresAt = req.ExpiresAt
}
if req.AutoPauseOnExpired != nil {
account.AutoPauseOnExpired = *req.AutoPauseOnExpired
}
// 先验证分组是否存在(在任何写操作之前) // 先验证分组是否存在(在任何写操作之前)
if req.GroupIDs != nil { if req.GroupIDs != nil {
......
...@@ -103,6 +103,10 @@ func (s *accountRepoStub) SetSchedulable(ctx context.Context, id int64, schedula ...@@ -103,6 +103,10 @@ func (s *accountRepoStub) SetSchedulable(ctx context.Context, id int64, schedula
panic("unexpected SetSchedulable call") panic("unexpected SetSchedulable call")
} }
func (s *accountRepoStub) AutoPauseExpiredAccounts(ctx context.Context, now time.Time) (int64, error) {
panic("unexpected AutoPauseExpiredAccounts call")
}
func (s *accountRepoStub) BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error { func (s *accountRepoStub) BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error {
panic("unexpected BindGroups call") panic("unexpected BindGroups call")
} }
......
...@@ -122,16 +122,18 @@ type UpdateGroupInput struct { ...@@ -122,16 +122,18 @@ type UpdateGroupInput struct {
} }
type CreateAccountInput struct { type CreateAccountInput struct {
Name string Name string
Notes *string Notes *string
Platform string Platform string
Type string Type string
Credentials map[string]any Credentials map[string]any
Extra map[string]any Extra map[string]any
ProxyID *int64 ProxyID *int64
Concurrency int Concurrency int
Priority int Priority int
GroupIDs []int64 GroupIDs []int64
ExpiresAt *int64
AutoPauseOnExpired *bool
// SkipMixedChannelCheck skips the mixed channel risk check when binding groups. // SkipMixedChannelCheck skips the mixed channel risk check when binding groups.
// This should only be set when the caller has explicitly confirmed the risk. // This should only be set when the caller has explicitly confirmed the risk.
SkipMixedChannelCheck bool SkipMixedChannelCheck bool
...@@ -148,6 +150,8 @@ type UpdateAccountInput struct { ...@@ -148,6 +150,8 @@ type UpdateAccountInput struct {
Priority *int // 使用指针区分"未提供"和"设置为0" Priority *int // 使用指针区分"未提供"和"设置为0"
Status string Status string
GroupIDs *[]int64 GroupIDs *[]int64
ExpiresAt *int64
AutoPauseOnExpired *bool
SkipMixedChannelCheck bool // 跳过混合渠道检查(用户已确认风险) SkipMixedChannelCheck bool // 跳过混合渠道检查(用户已确认风险)
} }
...@@ -700,6 +704,15 @@ func (s *adminServiceImpl) CreateAccount(ctx context.Context, input *CreateAccou ...@@ -700,6 +704,15 @@ func (s *adminServiceImpl) CreateAccount(ctx context.Context, input *CreateAccou
Status: StatusActive, Status: StatusActive,
Schedulable: true, Schedulable: true,
} }
if input.ExpiresAt != nil && *input.ExpiresAt > 0 {
expiresAt := time.Unix(*input.ExpiresAt, 0)
account.ExpiresAt = &expiresAt
}
if input.AutoPauseOnExpired != nil {
account.AutoPauseOnExpired = *input.AutoPauseOnExpired
} else {
account.AutoPauseOnExpired = true
}
if err := s.accountRepo.Create(ctx, account); err != nil { if err := s.accountRepo.Create(ctx, account); err != nil {
return nil, err return nil, err
} }
...@@ -755,6 +768,17 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U ...@@ -755,6 +768,17 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U
if input.Status != "" { if input.Status != "" {
account.Status = input.Status account.Status = input.Status
} }
if input.ExpiresAt != nil {
if *input.ExpiresAt <= 0 {
account.ExpiresAt = nil
} else {
expiresAt := time.Unix(*input.ExpiresAt, 0)
account.ExpiresAt = &expiresAt
}
}
if input.AutoPauseOnExpired != nil {
account.AutoPauseOnExpired = *input.AutoPauseOnExpired
}
// 先验证分组是否存在(在任何写操作之前) // 先验证分组是否存在(在任何写操作之前)
if input.GroupIDs != nil { if input.GroupIDs != nil {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment