mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 13:48:43 +08:00
feat(proxies): 代理有效期与失败回退
- schema/迁移: 代理有效期、提醒天数、失败回退配置 + 账号 fallback 来源字段 - service/repo/DTO/handler: CRUD 透传新字段 + 校验 - fallback 目标解析纯函数(链式解析 + 环检测 + 兜底) - SweepExpiredProxies 到期改投账号 + outbox 失效 - ProxyExpiryService 后台到期扫描任务 + wire 注册 - 账号侧手动回切原代理 + fallback 来源徽章/按钮 - 前端: 创建/编辑表单、列表到期徽章、类型/API/i18n - ops 告警: proxy_expired_count / proxy_expiring_soon_count 指标 - 导入导出携带有效期/回退字段(备用按 name 映射) - 补全测试 stub + 集成测试 + review 问题修复
This commit is contained in:
@@ -81,6 +81,7 @@ func provideCleanup(
|
||||
schedulerSnapshot *service.SchedulerSnapshotService,
|
||||
tokenRefresh *service.TokenRefreshService,
|
||||
accountExpiry *service.AccountExpiryService,
|
||||
proxyExpiry *service.ProxyExpiryService,
|
||||
subscriptionExpiry *service.SubscriptionExpiryService,
|
||||
usageCleanup *service.UsageCleanupService,
|
||||
idempotencyCleanup *service.IdempotencyCleanupService,
|
||||
@@ -173,6 +174,10 @@ func provideCleanup(
|
||||
accountExpiry.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"ProxyExpiryService", func() error {
|
||||
proxyExpiry.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"SubscriptionExpiryService", func() error {
|
||||
subscriptionExpiry.Stop()
|
||||
return nil
|
||||
|
||||
@@ -261,17 +261,18 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
httpServer := server.ProvideHTTPServer(configConfig, engine)
|
||||
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
|
||||
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
|
||||
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig)
|
||||
opsAlertEvaluatorService := service.ProvideOpsAlertEvaluatorService(opsService, opsRepository, emailService, redisClient, configConfig, proxyRepository)
|
||||
opsCleanupService := service.ProvideOpsCleanupService(opsRepository, db, redisClient, configConfig, channelMonitorService, settingRepository, opsService)
|
||||
opsScheduledReportService := service.ProvideOpsScheduledReportService(opsService, userService, emailService, redisClient, configConfig)
|
||||
tokenRefreshService := service.ProvideTokenRefreshService(accountRepository, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, compositeTokenCacheInvalidator, schedulerCache, configConfig, tempUnschedCache, privacyClientFactory, proxyRepository, oAuthRefreshAPI, openAIGatewayService)
|
||||
accountExpiryService := service.ProvideAccountExpiryService(accountRepository)
|
||||
proxyExpiryService := service.ProvideProxyExpiryService(proxyRepository)
|
||||
subscriptionExpiryService := service.ProvideSubscriptionExpiryService(userSubscriptionRepository, settingRepository, notificationEmailService, leaderLockCache, db)
|
||||
scheduledTestRunnerService := service.ProvideScheduledTestRunnerService(scheduledTestPlanRepository, scheduledTestService, accountTestService, rateLimitService, configConfig)
|
||||
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService, leaderLockCache, db)
|
||||
channelMonitorRunner := service.ProvideChannelMonitorRunner(channelMonitorService, settingService)
|
||||
userPlatformQuotaUsageFlusher := service.ProvideUserPlatformQuotaUsageFlusher(configConfig, billingCache, serviceUserPlatformQuotaRepository, timingWheelService)
|
||||
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService, channelMonitorRunner, userPlatformQuotaUsageFlusher)
|
||||
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, proxyExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService, channelMonitorRunner, userPlatformQuotaUsageFlusher)
|
||||
application := &Application{
|
||||
Server: httpServer,
|
||||
Cleanup: v,
|
||||
@@ -309,6 +310,7 @@ func provideCleanup(
|
||||
schedulerSnapshot *service.SchedulerSnapshotService,
|
||||
tokenRefresh *service.TokenRefreshService,
|
||||
accountExpiry *service.AccountExpiryService,
|
||||
proxyExpiry *service.ProxyExpiryService,
|
||||
subscriptionExpiry *service.SubscriptionExpiryService,
|
||||
usageCleanup *service.UsageCleanupService,
|
||||
idempotencyCleanup *service.IdempotencyCleanupService,
|
||||
@@ -400,6 +402,10 @@ func provideCleanup(
|
||||
accountExpiry.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"ProxyExpiryService", func() error {
|
||||
proxyExpiry.Stop()
|
||||
return nil
|
||||
}},
|
||||
{"SubscriptionExpiryService", func() error {
|
||||
subscriptionExpiry.Stop()
|
||||
return nil
|
||||
|
||||
@@ -40,6 +40,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
|
||||
nil,
|
||||
)
|
||||
accountExpirySvc := service.NewAccountExpiryService(nil, time.Second)
|
||||
proxyExpirySvc := service.NewProxyExpiryService(nil, time.Second)
|
||||
subscriptionExpirySvc := service.NewSubscriptionExpiryService(nil, time.Second)
|
||||
pricingSvc := service.NewPricingService(cfg, nil)
|
||||
emailQueueSvc := service.NewEmailQueueService(nil, 1)
|
||||
@@ -60,6 +61,7 @@ func TestProvideCleanup_WithMinimalDependencies_NoPanic(t *testing.T) {
|
||||
schedulerSnapshotSvc,
|
||||
tokenRefreshSvc,
|
||||
accountExpirySvc,
|
||||
proxyExpirySvc,
|
||||
subscriptionExpirySvc,
|
||||
&service.UsageCleanupService{},
|
||||
idempotencyCleanupSvc,
|
||||
|
||||
+15
-1
@@ -39,6 +39,8 @@ type Account struct {
|
||||
Extra map[string]interface{} `json:"extra,omitempty"`
|
||||
// ProxyID holds the value of the "proxy_id" field.
|
||||
ProxyID *int64 `json:"proxy_id,omitempty"`
|
||||
// Original proxy id replaced by expiry-fallback; for manual revert. NULL = not in fallback.
|
||||
ProxyFallbackOriginID *int64 `json:"proxy_fallback_origin_id,omitempty"`
|
||||
// Concurrency holds the value of the "concurrency" field.
|
||||
Concurrency int `json:"concurrency,omitempty"`
|
||||
// LoadFactor holds the value of the "load_factor" field.
|
||||
@@ -145,7 +147,7 @@ func (*Account) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new(sql.NullBool)
|
||||
case account.FieldRateMultiplier:
|
||||
values[i] = new(sql.NullFloat64)
|
||||
case account.FieldID, account.FieldProxyID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
|
||||
case account.FieldID, account.FieldProxyID, account.FieldProxyFallbackOriginID, account.FieldConcurrency, account.FieldLoadFactor, account.FieldPriority:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case account.FieldName, account.FieldNotes, account.FieldPlatform, account.FieldType, account.FieldStatus, account.FieldErrorMessage, account.FieldTempUnschedulableReason, account.FieldSessionWindowStatus:
|
||||
values[i] = new(sql.NullString)
|
||||
@@ -239,6 +241,13 @@ func (_m *Account) assignValues(columns []string, values []any) error {
|
||||
_m.ProxyID = new(int64)
|
||||
*_m.ProxyID = value.Int64
|
||||
}
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field proxy_fallback_origin_id", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ProxyFallbackOriginID = new(int64)
|
||||
*_m.ProxyFallbackOriginID = value.Int64
|
||||
}
|
||||
case account.FieldConcurrency:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field concurrency", values[i])
|
||||
@@ -451,6 +460,11 @@ func (_m *Account) String() string {
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.ProxyFallbackOriginID; v != nil {
|
||||
builder.WriteString("proxy_fallback_origin_id=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("concurrency=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Concurrency))
|
||||
builder.WriteString(", ")
|
||||
|
||||
@@ -35,6 +35,8 @@ const (
|
||||
FieldExtra = "extra"
|
||||
// FieldProxyID holds the string denoting the proxy_id field in the database.
|
||||
FieldProxyID = "proxy_id"
|
||||
// FieldProxyFallbackOriginID holds the string denoting the proxy_fallback_origin_id field in the database.
|
||||
FieldProxyFallbackOriginID = "proxy_fallback_origin_id"
|
||||
// FieldConcurrency holds the string denoting the concurrency field in the database.
|
||||
FieldConcurrency = "concurrency"
|
||||
// FieldLoadFactor holds the string denoting the load_factor field in the database.
|
||||
@@ -122,6 +124,7 @@ var Columns = []string{
|
||||
FieldCredentials,
|
||||
FieldExtra,
|
||||
FieldProxyID,
|
||||
FieldProxyFallbackOriginID,
|
||||
FieldConcurrency,
|
||||
FieldLoadFactor,
|
||||
FieldPriority,
|
||||
@@ -248,6 +251,11 @@ func ByProxyID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldProxyID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByProxyFallbackOriginID orders the results by the proxy_fallback_origin_id field.
|
||||
func ByProxyFallbackOriginID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldProxyFallbackOriginID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByConcurrency orders the results by the concurrency field.
|
||||
func ByConcurrency(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldConcurrency, opts...).ToFunc()
|
||||
|
||||
@@ -95,6 +95,11 @@ func ProxyID(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldProxyID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginID applies equality check predicate on the "proxy_fallback_origin_id" field. It's identical to ProxyFallbackOriginIDEQ.
|
||||
func ProxyFallbackOriginID(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// Concurrency applies equality check predicate on the "concurrency" field. It's identical to ConcurrencyEQ.
|
||||
func Concurrency(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
|
||||
@@ -615,6 +620,56 @@ func ProxyIDNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldProxyID))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDEQ applies the EQ predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDEQ(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDNEQ applies the NEQ predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDNEQ(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldNEQ(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDIn applies the In predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDIn(vs ...int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldIn(FieldProxyFallbackOriginID, vs...))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDNotIn applies the NotIn predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDNotIn(vs ...int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldNotIn(FieldProxyFallbackOriginID, vs...))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDGT applies the GT predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDGT(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldGT(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDGTE applies the GTE predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDGTE(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldGTE(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDLT applies the LT predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDLT(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldLT(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDLTE applies the LTE predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDLTE(v int64) predicate.Account {
|
||||
return predicate.Account(sql.FieldLTE(FieldProxyFallbackOriginID, v))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDIsNil applies the IsNil predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDIsNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldIsNull(FieldProxyFallbackOriginID))
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDNotNil applies the NotNil predicate on the "proxy_fallback_origin_id" field.
|
||||
func ProxyFallbackOriginIDNotNil() predicate.Account {
|
||||
return predicate.Account(sql.FieldNotNull(FieldProxyFallbackOriginID))
|
||||
}
|
||||
|
||||
// ConcurrencyEQ applies the EQ predicate on the "concurrency" field.
|
||||
func ConcurrencyEQ(v int) predicate.Account {
|
||||
return predicate.Account(sql.FieldEQ(FieldConcurrency, v))
|
||||
|
||||
@@ -125,6 +125,20 @@ func (_c *AccountCreate) SetNillableProxyID(v *int64) *AccountCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (_c *AccountCreate) SetProxyFallbackOriginID(v int64) *AccountCreate {
|
||||
_c.mutation.SetProxyFallbackOriginID(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableProxyFallbackOriginID sets the "proxy_fallback_origin_id" field if the given value is not nil.
|
||||
func (_c *AccountCreate) SetNillableProxyFallbackOriginID(v *int64) *AccountCreate {
|
||||
if v != nil {
|
||||
_c.SetProxyFallbackOriginID(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (_c *AccountCreate) SetConcurrency(v int) *AccountCreate {
|
||||
_c.mutation.SetConcurrency(v)
|
||||
@@ -633,6 +647,10 @@ func (_c *AccountCreate) createSpec() (*Account, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(account.FieldExtra, field.TypeJSON, value)
|
||||
_node.Extra = value
|
||||
}
|
||||
if value, ok := _c.mutation.ProxyFallbackOriginID(); ok {
|
||||
_spec.SetField(account.FieldProxyFallbackOriginID, field.TypeInt64, value)
|
||||
_node.ProxyFallbackOriginID = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Concurrency(); ok {
|
||||
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
||||
_node.Concurrency = value
|
||||
@@ -936,6 +954,30 @@ func (u *AccountUpsert) ClearProxyID() *AccountUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsert) SetProxyFallbackOriginID(v int64) *AccountUpsert {
|
||||
u.Set(account.FieldProxyFallbackOriginID, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateProxyFallbackOriginID sets the "proxy_fallback_origin_id" field to the value that was provided on create.
|
||||
func (u *AccountUpsert) UpdateProxyFallbackOriginID() *AccountUpsert {
|
||||
u.SetExcluded(account.FieldProxyFallbackOriginID)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds v to the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsert) AddProxyFallbackOriginID(v int64) *AccountUpsert {
|
||||
u.Add(account.FieldProxyFallbackOriginID, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsert) ClearProxyFallbackOriginID() *AccountUpsert {
|
||||
u.SetNull(account.FieldProxyFallbackOriginID)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (u *AccountUpsert) SetConcurrency(v int) *AccountUpsert {
|
||||
u.Set(account.FieldConcurrency, v)
|
||||
@@ -1440,6 +1482,34 @@ func (u *AccountUpsertOne) ClearProxyID() *AccountUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertOne) SetProxyFallbackOriginID(v int64) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetProxyFallbackOriginID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds v to the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertOne) AddProxyFallbackOriginID(v int64) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.AddProxyFallbackOriginID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProxyFallbackOriginID sets the "proxy_fallback_origin_id" field to the value that was provided on create.
|
||||
func (u *AccountUpsertOne) UpdateProxyFallbackOriginID() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateProxyFallbackOriginID()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertOne) ClearProxyFallbackOriginID() *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearProxyFallbackOriginID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (u *AccountUpsertOne) SetConcurrency(v int) *AccountUpsertOne {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
@@ -2162,6 +2232,34 @@ func (u *AccountUpsertBulk) ClearProxyID() *AccountUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertBulk) SetProxyFallbackOriginID(v int64) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.SetProxyFallbackOriginID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds v to the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertBulk) AddProxyFallbackOriginID(v int64) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.AddProxyFallbackOriginID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateProxyFallbackOriginID sets the "proxy_fallback_origin_id" field to the value that was provided on create.
|
||||
func (u *AccountUpsertBulk) UpdateProxyFallbackOriginID() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.UpdateProxyFallbackOriginID()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (u *AccountUpsertBulk) ClearProxyFallbackOriginID() *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
s.ClearProxyFallbackOriginID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (u *AccountUpsertBulk) SetConcurrency(v int) *AccountUpsertBulk {
|
||||
return u.Update(func(s *AccountUpsert) {
|
||||
|
||||
@@ -151,6 +151,33 @@ func (_u *AccountUpdate) ClearProxyID() *AccountUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdate) SetProxyFallbackOriginID(v int64) *AccountUpdate {
|
||||
_u.mutation.ResetProxyFallbackOriginID()
|
||||
_u.mutation.SetProxyFallbackOriginID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableProxyFallbackOriginID sets the "proxy_fallback_origin_id" field if the given value is not nil.
|
||||
func (_u *AccountUpdate) SetNillableProxyFallbackOriginID(v *int64) *AccountUpdate {
|
||||
if v != nil {
|
||||
_u.SetProxyFallbackOriginID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds value to the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdate) AddProxyFallbackOriginID(v int64) *AccountUpdate {
|
||||
_u.mutation.AddProxyFallbackOriginID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdate) ClearProxyFallbackOriginID() *AccountUpdate {
|
||||
_u.mutation.ClearProxyFallbackOriginID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (_u *AccountUpdate) SetConcurrency(v int) *AccountUpdate {
|
||||
_u.mutation.ResetConcurrency()
|
||||
@@ -705,6 +732,15 @@ func (_u *AccountUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if value, ok := _u.mutation.Extra(); ok {
|
||||
_spec.SetField(account.FieldExtra, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ProxyFallbackOriginID(); ok {
|
||||
_spec.SetField(account.FieldProxyFallbackOriginID, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedProxyFallbackOriginID(); ok {
|
||||
_spec.AddField(account.FieldProxyFallbackOriginID, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.ProxyFallbackOriginIDCleared() {
|
||||
_spec.ClearField(account.FieldProxyFallbackOriginID, field.TypeInt64)
|
||||
}
|
||||
if value, ok := _u.mutation.Concurrency(); ok {
|
||||
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
||||
}
|
||||
@@ -1078,6 +1114,33 @@ func (_u *AccountUpdateOne) ClearProxyID() *AccountUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdateOne) SetProxyFallbackOriginID(v int64) *AccountUpdateOne {
|
||||
_u.mutation.ResetProxyFallbackOriginID()
|
||||
_u.mutation.SetProxyFallbackOriginID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableProxyFallbackOriginID sets the "proxy_fallback_origin_id" field if the given value is not nil.
|
||||
func (_u *AccountUpdateOne) SetNillableProxyFallbackOriginID(v *int64) *AccountUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetProxyFallbackOriginID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds value to the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdateOne) AddProxyFallbackOriginID(v int64) *AccountUpdateOne {
|
||||
_u.mutation.AddProxyFallbackOriginID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (_u *AccountUpdateOne) ClearProxyFallbackOriginID() *AccountUpdateOne {
|
||||
_u.mutation.ClearProxyFallbackOriginID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (_u *AccountUpdateOne) SetConcurrency(v int) *AccountUpdateOne {
|
||||
_u.mutation.ResetConcurrency()
|
||||
@@ -1662,6 +1725,15 @@ func (_u *AccountUpdateOne) sqlSave(ctx context.Context) (_node *Account, err er
|
||||
if value, ok := _u.mutation.Extra(); ok {
|
||||
_spec.SetField(account.FieldExtra, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ProxyFallbackOriginID(); ok {
|
||||
_spec.SetField(account.FieldProxyFallbackOriginID, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedProxyFallbackOriginID(); ok {
|
||||
_spec.AddField(account.FieldProxyFallbackOriginID, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.ProxyFallbackOriginIDCleared() {
|
||||
_spec.ClearField(account.FieldProxyFallbackOriginID, field.TypeInt64)
|
||||
}
|
||||
if value, ok := _u.mutation.Concurrency(); ok {
|
||||
_spec.SetField(account.FieldConcurrency, field.TypeInt, value)
|
||||
}
|
||||
|
||||
@@ -3979,6 +3979,22 @@ func (c *ProxyClient) QueryAccounts(_m *Proxy) *AccountQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryBackupProxy queries the backup_proxy edge of a Proxy.
|
||||
func (c *ProxyClient) QueryBackupProxy(_m *Proxy) *ProxyQuery {
|
||||
query := (&ProxyClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(proxy.Table, proxy.FieldID, id),
|
||||
sqlgraph.To(proxy.Table, proxy.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2O, false, proxy.BackupProxyTable, proxy.BackupProxyColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *ProxyClient) Hooks() []Hook {
|
||||
hooks := c.hooks.Proxy
|
||||
|
||||
@@ -105,6 +105,7 @@ var (
|
||||
{Name: "type", Type: field.TypeString, Size: 20},
|
||||
{Name: "credentials", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||
{Name: "extra", Type: field.TypeJSON, SchemaType: map[string]string{"postgres": "jsonb"}},
|
||||
{Name: "proxy_fallback_origin_id", Type: field.TypeInt64, Nullable: true},
|
||||
{Name: "concurrency", Type: field.TypeInt, Default: 3},
|
||||
{Name: "load_factor", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "priority", Type: field.TypeInt, Default: 50},
|
||||
@@ -133,7 +134,7 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "accounts_proxies_proxy",
|
||||
Columns: []*schema.Column{AccountsColumns[28]},
|
||||
Columns: []*schema.Column{AccountsColumns[29]},
|
||||
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
@@ -152,52 +153,52 @@ var (
|
||||
{
|
||||
Name: "account_status",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[14]},
|
||||
Columns: []*schema.Column{AccountsColumns[15]},
|
||||
},
|
||||
{
|
||||
Name: "account_proxy_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[28]},
|
||||
Columns: []*schema.Column{AccountsColumns[29]},
|
||||
},
|
||||
{
|
||||
Name: "account_priority",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[12]},
|
||||
Columns: []*schema.Column{AccountsColumns[13]},
|
||||
},
|
||||
{
|
||||
Name: "account_last_used_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[16]},
|
||||
Columns: []*schema.Column{AccountsColumns[17]},
|
||||
},
|
||||
{
|
||||
Name: "account_schedulable",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[19]},
|
||||
Columns: []*schema.Column{AccountsColumns[20]},
|
||||
},
|
||||
{
|
||||
Name: "account_rate_limited_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[20]},
|
||||
Columns: []*schema.Column{AccountsColumns[21]},
|
||||
},
|
||||
{
|
||||
Name: "account_rate_limit_reset_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[21]},
|
||||
Columns: []*schema.Column{AccountsColumns[22]},
|
||||
},
|
||||
{
|
||||
Name: "account_overload_until",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[22]},
|
||||
Columns: []*schema.Column{AccountsColumns[23]},
|
||||
},
|
||||
{
|
||||
Name: "account_platform_priority",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[12]},
|
||||
Columns: []*schema.Column{AccountsColumns[6], AccountsColumns[13]},
|
||||
},
|
||||
{
|
||||
Name: "account_priority_status",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{AccountsColumns[12], AccountsColumns[14]},
|
||||
Columns: []*schema.Column{AccountsColumns[13], AccountsColumns[15]},
|
||||
},
|
||||
{
|
||||
Name: "account_deleted_at",
|
||||
@@ -1104,12 +1105,24 @@ var (
|
||||
{Name: "username", Type: field.TypeString, Nullable: true, Size: 100},
|
||||
{Name: "password", Type: field.TypeString, Nullable: true, Size: 100},
|
||||
{Name: "status", Type: field.TypeString, Size: 20, Default: "active"},
|
||||
{Name: "expires_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "fallback_mode", Type: field.TypeString, Size: 20, Default: "none"},
|
||||
{Name: "expiry_warn_days", Type: field.TypeInt, Default: 7},
|
||||
{Name: "backup_proxy_id", Type: field.TypeInt64, Unique: true, Nullable: true},
|
||||
}
|
||||
// ProxiesTable holds the schema information for the "proxies" table.
|
||||
ProxiesTable = &schema.Table{
|
||||
Name: "proxies",
|
||||
Columns: ProxiesColumns,
|
||||
PrimaryKey: []*schema.Column{ProxiesColumns[0]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "proxies_proxies_backup_proxy",
|
||||
Columns: []*schema.Column{ProxiesColumns[14]},
|
||||
RefColumns: []*schema.Column{ProxiesColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "proxy_status",
|
||||
@@ -1121,6 +1134,16 @@ var (
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ProxiesColumns[3]},
|
||||
},
|
||||
{
|
||||
Name: "proxy_expires_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ProxiesColumns[11]},
|
||||
},
|
||||
{
|
||||
Name: "proxy_backup_proxy_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ProxiesColumns[14]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// RedeemCodesColumns holds the columns for the "redeem_codes" table.
|
||||
@@ -1871,6 +1894,7 @@ func init() {
|
||||
PromoCodeUsagesTable.Annotation = &entsql.Annotation{
|
||||
Table: "promo_code_usages",
|
||||
}
|
||||
ProxiesTable.ForeignKeys[0].RefTable = ProxiesTable
|
||||
ProxiesTable.Annotation = &entsql.Annotation{
|
||||
Table: "proxies",
|
||||
}
|
||||
|
||||
+512
-72
@@ -2274,52 +2274,54 @@ func (m *APIKeyMutation) ResetEdge(name string) error {
|
||||
// AccountMutation represents an operation that mutates the Account nodes in the graph.
|
||||
type AccountMutation struct {
|
||||
config
|
||||
op Op
|
||||
typ string
|
||||
id *int64
|
||||
created_at *time.Time
|
||||
updated_at *time.Time
|
||||
deleted_at *time.Time
|
||||
name *string
|
||||
notes *string
|
||||
platform *string
|
||||
_type *string
|
||||
credentials *map[string]interface{}
|
||||
extra *map[string]interface{}
|
||||
concurrency *int
|
||||
addconcurrency *int
|
||||
load_factor *int
|
||||
addload_factor *int
|
||||
priority *int
|
||||
addpriority *int
|
||||
rate_multiplier *float64
|
||||
addrate_multiplier *float64
|
||||
status *string
|
||||
error_message *string
|
||||
last_used_at *time.Time
|
||||
expires_at *time.Time
|
||||
auto_pause_on_expired *bool
|
||||
schedulable *bool
|
||||
rate_limited_at *time.Time
|
||||
rate_limit_reset_at *time.Time
|
||||
overload_until *time.Time
|
||||
temp_unschedulable_until *time.Time
|
||||
temp_unschedulable_reason *string
|
||||
session_window_start *time.Time
|
||||
session_window_end *time.Time
|
||||
session_window_status *string
|
||||
clearedFields map[string]struct{}
|
||||
groups map[int64]struct{}
|
||||
removedgroups map[int64]struct{}
|
||||
clearedgroups bool
|
||||
proxy *int64
|
||||
clearedproxy bool
|
||||
usage_logs map[int64]struct{}
|
||||
removedusage_logs map[int64]struct{}
|
||||
clearedusage_logs bool
|
||||
done bool
|
||||
oldValue func(context.Context) (*Account, error)
|
||||
predicates []predicate.Account
|
||||
op Op
|
||||
typ string
|
||||
id *int64
|
||||
created_at *time.Time
|
||||
updated_at *time.Time
|
||||
deleted_at *time.Time
|
||||
name *string
|
||||
notes *string
|
||||
platform *string
|
||||
_type *string
|
||||
credentials *map[string]interface{}
|
||||
extra *map[string]interface{}
|
||||
proxy_fallback_origin_id *int64
|
||||
addproxy_fallback_origin_id *int64
|
||||
concurrency *int
|
||||
addconcurrency *int
|
||||
load_factor *int
|
||||
addload_factor *int
|
||||
priority *int
|
||||
addpriority *int
|
||||
rate_multiplier *float64
|
||||
addrate_multiplier *float64
|
||||
status *string
|
||||
error_message *string
|
||||
last_used_at *time.Time
|
||||
expires_at *time.Time
|
||||
auto_pause_on_expired *bool
|
||||
schedulable *bool
|
||||
rate_limited_at *time.Time
|
||||
rate_limit_reset_at *time.Time
|
||||
overload_until *time.Time
|
||||
temp_unschedulable_until *time.Time
|
||||
temp_unschedulable_reason *string
|
||||
session_window_start *time.Time
|
||||
session_window_end *time.Time
|
||||
session_window_status *string
|
||||
clearedFields map[string]struct{}
|
||||
groups map[int64]struct{}
|
||||
removedgroups map[int64]struct{}
|
||||
clearedgroups bool
|
||||
proxy *int64
|
||||
clearedproxy bool
|
||||
usage_logs map[int64]struct{}
|
||||
removedusage_logs map[int64]struct{}
|
||||
clearedusage_logs bool
|
||||
done bool
|
||||
oldValue func(context.Context) (*Account, error)
|
||||
predicates []predicate.Account
|
||||
}
|
||||
|
||||
var _ ent.Mutation = (*AccountMutation)(nil)
|
||||
@@ -2819,6 +2821,76 @@ func (m *AccountMutation) ResetProxyID() {
|
||||
delete(m.clearedFields, account.FieldProxyID)
|
||||
}
|
||||
|
||||
// SetProxyFallbackOriginID sets the "proxy_fallback_origin_id" field.
|
||||
func (m *AccountMutation) SetProxyFallbackOriginID(i int64) {
|
||||
m.proxy_fallback_origin_id = &i
|
||||
m.addproxy_fallback_origin_id = nil
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginID returns the value of the "proxy_fallback_origin_id" field in the mutation.
|
||||
func (m *AccountMutation) ProxyFallbackOriginID() (r int64, exists bool) {
|
||||
v := m.proxy_fallback_origin_id
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldProxyFallbackOriginID returns the old "proxy_fallback_origin_id" 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) OldProxyFallbackOriginID(ctx context.Context) (v *int64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldProxyFallbackOriginID is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldProxyFallbackOriginID requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldProxyFallbackOriginID: %w", err)
|
||||
}
|
||||
return oldValue.ProxyFallbackOriginID, nil
|
||||
}
|
||||
|
||||
// AddProxyFallbackOriginID adds i to the "proxy_fallback_origin_id" field.
|
||||
func (m *AccountMutation) AddProxyFallbackOriginID(i int64) {
|
||||
if m.addproxy_fallback_origin_id != nil {
|
||||
*m.addproxy_fallback_origin_id += i
|
||||
} else {
|
||||
m.addproxy_fallback_origin_id = &i
|
||||
}
|
||||
}
|
||||
|
||||
// AddedProxyFallbackOriginID returns the value that was added to the "proxy_fallback_origin_id" field in this mutation.
|
||||
func (m *AccountMutation) AddedProxyFallbackOriginID() (r int64, exists bool) {
|
||||
v := m.addproxy_fallback_origin_id
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ClearProxyFallbackOriginID clears the value of the "proxy_fallback_origin_id" field.
|
||||
func (m *AccountMutation) ClearProxyFallbackOriginID() {
|
||||
m.proxy_fallback_origin_id = nil
|
||||
m.addproxy_fallback_origin_id = nil
|
||||
m.clearedFields[account.FieldProxyFallbackOriginID] = struct{}{}
|
||||
}
|
||||
|
||||
// ProxyFallbackOriginIDCleared returns if the "proxy_fallback_origin_id" field was cleared in this mutation.
|
||||
func (m *AccountMutation) ProxyFallbackOriginIDCleared() bool {
|
||||
_, ok := m.clearedFields[account.FieldProxyFallbackOriginID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetProxyFallbackOriginID resets all changes to the "proxy_fallback_origin_id" field.
|
||||
func (m *AccountMutation) ResetProxyFallbackOriginID() {
|
||||
m.proxy_fallback_origin_id = nil
|
||||
m.addproxy_fallback_origin_id = nil
|
||||
delete(m.clearedFields, account.FieldProxyFallbackOriginID)
|
||||
}
|
||||
|
||||
// SetConcurrency sets the "concurrency" field.
|
||||
func (m *AccountMutation) SetConcurrency(i int) {
|
||||
m.concurrency = &i
|
||||
@@ -3873,7 +3945,7 @@ func (m *AccountMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *AccountMutation) Fields() []string {
|
||||
fields := make([]string, 0, 28)
|
||||
fields := make([]string, 0, 29)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, account.FieldCreatedAt)
|
||||
}
|
||||
@@ -3904,6 +3976,9 @@ func (m *AccountMutation) Fields() []string {
|
||||
if m.proxy != nil {
|
||||
fields = append(fields, account.FieldProxyID)
|
||||
}
|
||||
if m.proxy_fallback_origin_id != nil {
|
||||
fields = append(fields, account.FieldProxyFallbackOriginID)
|
||||
}
|
||||
if m.concurrency != nil {
|
||||
fields = append(fields, account.FieldConcurrency)
|
||||
}
|
||||
@@ -3986,6 +4061,8 @@ func (m *AccountMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.Extra()
|
||||
case account.FieldProxyID:
|
||||
return m.ProxyID()
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
return m.ProxyFallbackOriginID()
|
||||
case account.FieldConcurrency:
|
||||
return m.Concurrency()
|
||||
case account.FieldLoadFactor:
|
||||
@@ -4051,6 +4128,8 @@ func (m *AccountMutation) OldField(ctx context.Context, name string) (ent.Value,
|
||||
return m.OldExtra(ctx)
|
||||
case account.FieldProxyID:
|
||||
return m.OldProxyID(ctx)
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
return m.OldProxyFallbackOriginID(ctx)
|
||||
case account.FieldConcurrency:
|
||||
return m.OldConcurrency(ctx)
|
||||
case account.FieldLoadFactor:
|
||||
@@ -4166,6 +4245,13 @@ func (m *AccountMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetProxyID(v)
|
||||
return nil
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetProxyFallbackOriginID(v)
|
||||
return nil
|
||||
case account.FieldConcurrency:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
@@ -4300,6 +4386,9 @@ func (m *AccountMutation) SetField(name string, value ent.Value) error {
|
||||
// this mutation.
|
||||
func (m *AccountMutation) AddedFields() []string {
|
||||
var fields []string
|
||||
if m.addproxy_fallback_origin_id != nil {
|
||||
fields = append(fields, account.FieldProxyFallbackOriginID)
|
||||
}
|
||||
if m.addconcurrency != nil {
|
||||
fields = append(fields, account.FieldConcurrency)
|
||||
}
|
||||
@@ -4320,6 +4409,8 @@ func (m *AccountMutation) AddedFields() []string {
|
||||
// was not set, or was not defined in the schema.
|
||||
func (m *AccountMutation) AddedField(name string) (ent.Value, bool) {
|
||||
switch name {
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
return m.AddedProxyFallbackOriginID()
|
||||
case account.FieldConcurrency:
|
||||
return m.AddedConcurrency()
|
||||
case account.FieldLoadFactor:
|
||||
@@ -4337,6 +4428,13 @@ func (m *AccountMutation) AddedField(name string) (ent.Value, bool) {
|
||||
// type.
|
||||
func (m *AccountMutation) AddField(name string, value ent.Value) error {
|
||||
switch name {
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddProxyFallbackOriginID(v)
|
||||
return nil
|
||||
case account.FieldConcurrency:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
@@ -4382,6 +4480,9 @@ func (m *AccountMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(account.FieldProxyID) {
|
||||
fields = append(fields, account.FieldProxyID)
|
||||
}
|
||||
if m.FieldCleared(account.FieldProxyFallbackOriginID) {
|
||||
fields = append(fields, account.FieldProxyFallbackOriginID)
|
||||
}
|
||||
if m.FieldCleared(account.FieldLoadFactor) {
|
||||
fields = append(fields, account.FieldLoadFactor)
|
||||
}
|
||||
@@ -4441,6 +4542,9 @@ func (m *AccountMutation) ClearField(name string) error {
|
||||
case account.FieldProxyID:
|
||||
m.ClearProxyID()
|
||||
return nil
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
m.ClearProxyFallbackOriginID()
|
||||
return nil
|
||||
case account.FieldLoadFactor:
|
||||
m.ClearLoadFactor()
|
||||
return nil
|
||||
@@ -4515,6 +4619,9 @@ func (m *AccountMutation) ResetField(name string) error {
|
||||
case account.FieldProxyID:
|
||||
m.ResetProxyID()
|
||||
return nil
|
||||
case account.FieldProxyFallbackOriginID:
|
||||
m.ResetProxyFallbackOriginID()
|
||||
return nil
|
||||
case account.FieldConcurrency:
|
||||
m.ResetConcurrency()
|
||||
return nil
|
||||
@@ -27754,27 +27861,33 @@ func (m *PromoCodeUsageMutation) ResetEdge(name string) error {
|
||||
// ProxyMutation represents an operation that mutates the Proxy nodes in the graph.
|
||||
type ProxyMutation struct {
|
||||
config
|
||||
op Op
|
||||
typ string
|
||||
id *int64
|
||||
created_at *time.Time
|
||||
updated_at *time.Time
|
||||
deleted_at *time.Time
|
||||
name *string
|
||||
protocol *string
|
||||
host *string
|
||||
port *int
|
||||
addport *int
|
||||
username *string
|
||||
password *string
|
||||
status *string
|
||||
clearedFields map[string]struct{}
|
||||
accounts map[int64]struct{}
|
||||
removedaccounts map[int64]struct{}
|
||||
clearedaccounts bool
|
||||
done bool
|
||||
oldValue func(context.Context) (*Proxy, error)
|
||||
predicates []predicate.Proxy
|
||||
op Op
|
||||
typ string
|
||||
id *int64
|
||||
created_at *time.Time
|
||||
updated_at *time.Time
|
||||
deleted_at *time.Time
|
||||
name *string
|
||||
protocol *string
|
||||
host *string
|
||||
port *int
|
||||
addport *int
|
||||
username *string
|
||||
password *string
|
||||
status *string
|
||||
expires_at *time.Time
|
||||
fallback_mode *string
|
||||
expiry_warn_days *int
|
||||
addexpiry_warn_days *int
|
||||
clearedFields map[string]struct{}
|
||||
accounts map[int64]struct{}
|
||||
removedaccounts map[int64]struct{}
|
||||
clearedaccounts bool
|
||||
backup_proxy *int64
|
||||
clearedbackup_proxy bool
|
||||
done bool
|
||||
oldValue func(context.Context) (*Proxy, error)
|
||||
predicates []predicate.Proxy
|
||||
}
|
||||
|
||||
var _ ent.Mutation = (*ProxyMutation)(nil)
|
||||
@@ -28294,6 +28407,196 @@ func (m *ProxyMutation) ResetStatus() {
|
||||
m.status = nil
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (m *ProxyMutation) SetExpiresAt(t time.Time) {
|
||||
m.expires_at = &t
|
||||
}
|
||||
|
||||
// ExpiresAt returns the value of the "expires_at" field in the mutation.
|
||||
func (m *ProxyMutation) 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 Proxy entity.
|
||||
// If the Proxy 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 *ProxyMutation) 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 *ProxyMutation) ClearExpiresAt() {
|
||||
m.expires_at = nil
|
||||
m.clearedFields[proxy.FieldExpiresAt] = struct{}{}
|
||||
}
|
||||
|
||||
// ExpiresAtCleared returns if the "expires_at" field was cleared in this mutation.
|
||||
func (m *ProxyMutation) ExpiresAtCleared() bool {
|
||||
_, ok := m.clearedFields[proxy.FieldExpiresAt]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetExpiresAt resets all changes to the "expires_at" field.
|
||||
func (m *ProxyMutation) ResetExpiresAt() {
|
||||
m.expires_at = nil
|
||||
delete(m.clearedFields, proxy.FieldExpiresAt)
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (m *ProxyMutation) SetFallbackMode(s string) {
|
||||
m.fallback_mode = &s
|
||||
}
|
||||
|
||||
// FallbackMode returns the value of the "fallback_mode" field in the mutation.
|
||||
func (m *ProxyMutation) FallbackMode() (r string, exists bool) {
|
||||
v := m.fallback_mode
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldFallbackMode returns the old "fallback_mode" field's value of the Proxy entity.
|
||||
// If the Proxy 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 *ProxyMutation) OldFallbackMode(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldFallbackMode is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldFallbackMode requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldFallbackMode: %w", err)
|
||||
}
|
||||
return oldValue.FallbackMode, nil
|
||||
}
|
||||
|
||||
// ResetFallbackMode resets all changes to the "fallback_mode" field.
|
||||
func (m *ProxyMutation) ResetFallbackMode() {
|
||||
m.fallback_mode = nil
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (m *ProxyMutation) SetBackupProxyID(i int64) {
|
||||
m.backup_proxy = &i
|
||||
}
|
||||
|
||||
// BackupProxyID returns the value of the "backup_proxy_id" field in the mutation.
|
||||
func (m *ProxyMutation) BackupProxyID() (r int64, exists bool) {
|
||||
v := m.backup_proxy
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldBackupProxyID returns the old "backup_proxy_id" field's value of the Proxy entity.
|
||||
// If the Proxy 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 *ProxyMutation) OldBackupProxyID(ctx context.Context) (v *int64, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldBackupProxyID is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldBackupProxyID requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldBackupProxyID: %w", err)
|
||||
}
|
||||
return oldValue.BackupProxyID, nil
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (m *ProxyMutation) ClearBackupProxyID() {
|
||||
m.backup_proxy = nil
|
||||
m.clearedFields[proxy.FieldBackupProxyID] = struct{}{}
|
||||
}
|
||||
|
||||
// BackupProxyIDCleared returns if the "backup_proxy_id" field was cleared in this mutation.
|
||||
func (m *ProxyMutation) BackupProxyIDCleared() bool {
|
||||
_, ok := m.clearedFields[proxy.FieldBackupProxyID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ResetBackupProxyID resets all changes to the "backup_proxy_id" field.
|
||||
func (m *ProxyMutation) ResetBackupProxyID() {
|
||||
m.backup_proxy = nil
|
||||
delete(m.clearedFields, proxy.FieldBackupProxyID)
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (m *ProxyMutation) SetExpiryWarnDays(i int) {
|
||||
m.expiry_warn_days = &i
|
||||
m.addexpiry_warn_days = nil
|
||||
}
|
||||
|
||||
// ExpiryWarnDays returns the value of the "expiry_warn_days" field in the mutation.
|
||||
func (m *ProxyMutation) ExpiryWarnDays() (r int, exists bool) {
|
||||
v := m.expiry_warn_days
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldExpiryWarnDays returns the old "expiry_warn_days" field's value of the Proxy entity.
|
||||
// If the Proxy 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 *ProxyMutation) OldExpiryWarnDays(ctx context.Context) (v int, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldExpiryWarnDays is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldExpiryWarnDays requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldExpiryWarnDays: %w", err)
|
||||
}
|
||||
return oldValue.ExpiryWarnDays, nil
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds i to the "expiry_warn_days" field.
|
||||
func (m *ProxyMutation) AddExpiryWarnDays(i int) {
|
||||
if m.addexpiry_warn_days != nil {
|
||||
*m.addexpiry_warn_days += i
|
||||
} else {
|
||||
m.addexpiry_warn_days = &i
|
||||
}
|
||||
}
|
||||
|
||||
// AddedExpiryWarnDays returns the value that was added to the "expiry_warn_days" field in this mutation.
|
||||
func (m *ProxyMutation) AddedExpiryWarnDays() (r int, exists bool) {
|
||||
v := m.addexpiry_warn_days
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ResetExpiryWarnDays resets all changes to the "expiry_warn_days" field.
|
||||
func (m *ProxyMutation) ResetExpiryWarnDays() {
|
||||
m.expiry_warn_days = nil
|
||||
m.addexpiry_warn_days = nil
|
||||
}
|
||||
|
||||
// AddAccountIDs adds the "accounts" edge to the Account entity by ids.
|
||||
func (m *ProxyMutation) AddAccountIDs(ids ...int64) {
|
||||
if m.accounts == nil {
|
||||
@@ -28348,6 +28651,33 @@ func (m *ProxyMutation) ResetAccounts() {
|
||||
m.removedaccounts = nil
|
||||
}
|
||||
|
||||
// ClearBackupProxy clears the "backup_proxy" edge to the Proxy entity.
|
||||
func (m *ProxyMutation) ClearBackupProxy() {
|
||||
m.clearedbackup_proxy = true
|
||||
m.clearedFields[proxy.FieldBackupProxyID] = struct{}{}
|
||||
}
|
||||
|
||||
// BackupProxyCleared reports if the "backup_proxy" edge to the Proxy entity was cleared.
|
||||
func (m *ProxyMutation) BackupProxyCleared() bool {
|
||||
return m.BackupProxyIDCleared() || m.clearedbackup_proxy
|
||||
}
|
||||
|
||||
// BackupProxyIDs returns the "backup_proxy" edge IDs in the mutation.
|
||||
// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use
|
||||
// BackupProxyID instead. It exists only for internal usage by the builders.
|
||||
func (m *ProxyMutation) BackupProxyIDs() (ids []int64) {
|
||||
if id := m.backup_proxy; id != nil {
|
||||
ids = append(ids, *id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResetBackupProxy resets all changes to the "backup_proxy" edge.
|
||||
func (m *ProxyMutation) ResetBackupProxy() {
|
||||
m.backup_proxy = nil
|
||||
m.clearedbackup_proxy = false
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ProxyMutation builder.
|
||||
func (m *ProxyMutation) Where(ps ...predicate.Proxy) {
|
||||
m.predicates = append(m.predicates, ps...)
|
||||
@@ -28382,7 +28712,7 @@ func (m *ProxyMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *ProxyMutation) Fields() []string {
|
||||
fields := make([]string, 0, 10)
|
||||
fields := make([]string, 0, 14)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, proxy.FieldCreatedAt)
|
||||
}
|
||||
@@ -28413,6 +28743,18 @@ func (m *ProxyMutation) Fields() []string {
|
||||
if m.status != nil {
|
||||
fields = append(fields, proxy.FieldStatus)
|
||||
}
|
||||
if m.expires_at != nil {
|
||||
fields = append(fields, proxy.FieldExpiresAt)
|
||||
}
|
||||
if m.fallback_mode != nil {
|
||||
fields = append(fields, proxy.FieldFallbackMode)
|
||||
}
|
||||
if m.backup_proxy != nil {
|
||||
fields = append(fields, proxy.FieldBackupProxyID)
|
||||
}
|
||||
if m.expiry_warn_days != nil {
|
||||
fields = append(fields, proxy.FieldExpiryWarnDays)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -28441,6 +28783,14 @@ func (m *ProxyMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.Password()
|
||||
case proxy.FieldStatus:
|
||||
return m.Status()
|
||||
case proxy.FieldExpiresAt:
|
||||
return m.ExpiresAt()
|
||||
case proxy.FieldFallbackMode:
|
||||
return m.FallbackMode()
|
||||
case proxy.FieldBackupProxyID:
|
||||
return m.BackupProxyID()
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
return m.ExpiryWarnDays()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -28470,6 +28820,14 @@ func (m *ProxyMutation) OldField(ctx context.Context, name string) (ent.Value, e
|
||||
return m.OldPassword(ctx)
|
||||
case proxy.FieldStatus:
|
||||
return m.OldStatus(ctx)
|
||||
case proxy.FieldExpiresAt:
|
||||
return m.OldExpiresAt(ctx)
|
||||
case proxy.FieldFallbackMode:
|
||||
return m.OldFallbackMode(ctx)
|
||||
case proxy.FieldBackupProxyID:
|
||||
return m.OldBackupProxyID(ctx)
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
return m.OldExpiryWarnDays(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown Proxy field %s", name)
|
||||
}
|
||||
@@ -28549,6 +28907,34 @@ func (m *ProxyMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetStatus(v)
|
||||
return nil
|
||||
case proxy.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 proxy.FieldFallbackMode:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetFallbackMode(v)
|
||||
return nil
|
||||
case proxy.FieldBackupProxyID:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetBackupProxyID(v)
|
||||
return nil
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetExpiryWarnDays(v)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy field %s", name)
|
||||
}
|
||||
@@ -28560,6 +28946,9 @@ func (m *ProxyMutation) AddedFields() []string {
|
||||
if m.addport != nil {
|
||||
fields = append(fields, proxy.FieldPort)
|
||||
}
|
||||
if m.addexpiry_warn_days != nil {
|
||||
fields = append(fields, proxy.FieldExpiryWarnDays)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -28570,6 +28959,8 @@ func (m *ProxyMutation) AddedField(name string) (ent.Value, bool) {
|
||||
switch name {
|
||||
case proxy.FieldPort:
|
||||
return m.AddedPort()
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
return m.AddedExpiryWarnDays()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -28586,6 +28977,13 @@ func (m *ProxyMutation) AddField(name string, value ent.Value) error {
|
||||
}
|
||||
m.AddPort(v)
|
||||
return nil
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddExpiryWarnDays(v)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy numeric field %s", name)
|
||||
}
|
||||
@@ -28603,6 +29001,12 @@ func (m *ProxyMutation) ClearedFields() []string {
|
||||
if m.FieldCleared(proxy.FieldPassword) {
|
||||
fields = append(fields, proxy.FieldPassword)
|
||||
}
|
||||
if m.FieldCleared(proxy.FieldExpiresAt) {
|
||||
fields = append(fields, proxy.FieldExpiresAt)
|
||||
}
|
||||
if m.FieldCleared(proxy.FieldBackupProxyID) {
|
||||
fields = append(fields, proxy.FieldBackupProxyID)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -28626,6 +29030,12 @@ func (m *ProxyMutation) ClearField(name string) error {
|
||||
case proxy.FieldPassword:
|
||||
m.ClearPassword()
|
||||
return nil
|
||||
case proxy.FieldExpiresAt:
|
||||
m.ClearExpiresAt()
|
||||
return nil
|
||||
case proxy.FieldBackupProxyID:
|
||||
m.ClearBackupProxyID()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy nullable field %s", name)
|
||||
}
|
||||
@@ -28664,16 +29074,31 @@ func (m *ProxyMutation) ResetField(name string) error {
|
||||
case proxy.FieldStatus:
|
||||
m.ResetStatus()
|
||||
return nil
|
||||
case proxy.FieldExpiresAt:
|
||||
m.ResetExpiresAt()
|
||||
return nil
|
||||
case proxy.FieldFallbackMode:
|
||||
m.ResetFallbackMode()
|
||||
return nil
|
||||
case proxy.FieldBackupProxyID:
|
||||
m.ResetBackupProxyID()
|
||||
return nil
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
m.ResetExpiryWarnDays()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy field %s", name)
|
||||
}
|
||||
|
||||
// AddedEdges returns all edge names that were set/added in this mutation.
|
||||
func (m *ProxyMutation) AddedEdges() []string {
|
||||
edges := make([]string, 0, 1)
|
||||
edges := make([]string, 0, 2)
|
||||
if m.accounts != nil {
|
||||
edges = append(edges, proxy.EdgeAccounts)
|
||||
}
|
||||
if m.backup_proxy != nil {
|
||||
edges = append(edges, proxy.EdgeBackupProxy)
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
||||
@@ -28687,13 +29112,17 @@ func (m *ProxyMutation) AddedIDs(name string) []ent.Value {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids
|
||||
case proxy.EdgeBackupProxy:
|
||||
if id := m.backup_proxy; id != nil {
|
||||
return []ent.Value{*id}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovedEdges returns all edge names that were removed in this mutation.
|
||||
func (m *ProxyMutation) RemovedEdges() []string {
|
||||
edges := make([]string, 0, 1)
|
||||
edges := make([]string, 0, 2)
|
||||
if m.removedaccounts != nil {
|
||||
edges = append(edges, proxy.EdgeAccounts)
|
||||
}
|
||||
@@ -28716,10 +29145,13 @@ func (m *ProxyMutation) RemovedIDs(name string) []ent.Value {
|
||||
|
||||
// ClearedEdges returns all edge names that were cleared in this mutation.
|
||||
func (m *ProxyMutation) ClearedEdges() []string {
|
||||
edges := make([]string, 0, 1)
|
||||
edges := make([]string, 0, 2)
|
||||
if m.clearedaccounts {
|
||||
edges = append(edges, proxy.EdgeAccounts)
|
||||
}
|
||||
if m.clearedbackup_proxy {
|
||||
edges = append(edges, proxy.EdgeBackupProxy)
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
||||
@@ -28729,6 +29161,8 @@ func (m *ProxyMutation) EdgeCleared(name string) bool {
|
||||
switch name {
|
||||
case proxy.EdgeAccounts:
|
||||
return m.clearedaccounts
|
||||
case proxy.EdgeBackupProxy:
|
||||
return m.clearedbackup_proxy
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -28737,6 +29171,9 @@ func (m *ProxyMutation) EdgeCleared(name string) bool {
|
||||
// if that edge is not defined in the schema.
|
||||
func (m *ProxyMutation) ClearEdge(name string) error {
|
||||
switch name {
|
||||
case proxy.EdgeBackupProxy:
|
||||
m.ClearBackupProxy()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy unique edge %s", name)
|
||||
}
|
||||
@@ -28748,6 +29185,9 @@ func (m *ProxyMutation) ResetEdge(name string) error {
|
||||
case proxy.EdgeAccounts:
|
||||
m.ResetAccounts()
|
||||
return nil
|
||||
case proxy.EdgeBackupProxy:
|
||||
m.ResetBackupProxy()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown Proxy edge %s", name)
|
||||
}
|
||||
|
||||
+72
-4
@@ -37,6 +37,14 @@ type Proxy struct {
|
||||
Password *string `json:"password,omitempty"`
|
||||
// Status holds the value of the "status" field.
|
||||
Status string `json:"status,omitempty"`
|
||||
// Proxy expiration time (NULL means never expires).
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
// Fallback target on expiry: none | proxy | direct.
|
||||
FallbackMode string `json:"fallback_mode,omitempty"`
|
||||
// Backup proxy id when fallback_mode=proxy (self-reference).
|
||||
BackupProxyID *int64 `json:"backup_proxy_id,omitempty"`
|
||||
// Days before expiry to flag as expiring-soon (per proxy).
|
||||
ExpiryWarnDays int `json:"expiry_warn_days,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the ProxyQuery when eager-loading is set.
|
||||
Edges ProxyEdges `json:"edges"`
|
||||
@@ -47,9 +55,11 @@ type Proxy struct {
|
||||
type ProxyEdges struct {
|
||||
// Accounts holds the value of the accounts edge.
|
||||
Accounts []*Account `json:"accounts,omitempty"`
|
||||
// BackupProxy holds the value of the backup_proxy edge.
|
||||
BackupProxy *Proxy `json:"backup_proxy,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
loadedTypes [2]bool
|
||||
}
|
||||
|
||||
// AccountsOrErr returns the Accounts value or an error if the edge
|
||||
@@ -61,16 +71,27 @@ func (e ProxyEdges) AccountsOrErr() ([]*Account, error) {
|
||||
return nil, &NotLoadedError{edge: "accounts"}
|
||||
}
|
||||
|
||||
// BackupProxyOrErr returns the BackupProxy value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e ProxyEdges) BackupProxyOrErr() (*Proxy, error) {
|
||||
if e.BackupProxy != nil {
|
||||
return e.BackupProxy, nil
|
||||
} else if e.loadedTypes[1] {
|
||||
return nil, &NotFoundError{label: proxy.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "backup_proxy"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*Proxy) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case proxy.FieldID, proxy.FieldPort:
|
||||
case proxy.FieldID, proxy.FieldPort, proxy.FieldBackupProxyID, proxy.FieldExpiryWarnDays:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case proxy.FieldName, proxy.FieldProtocol, proxy.FieldHost, proxy.FieldUsername, proxy.FieldPassword, proxy.FieldStatus:
|
||||
case proxy.FieldName, proxy.FieldProtocol, proxy.FieldHost, proxy.FieldUsername, proxy.FieldPassword, proxy.FieldStatus, proxy.FieldFallbackMode:
|
||||
values[i] = new(sql.NullString)
|
||||
case proxy.FieldCreatedAt, proxy.FieldUpdatedAt, proxy.FieldDeletedAt:
|
||||
case proxy.FieldCreatedAt, proxy.FieldUpdatedAt, proxy.FieldDeletedAt, proxy.FieldExpiresAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
@@ -156,6 +177,32 @@ func (_m *Proxy) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.Status = value.String
|
||||
}
|
||||
case proxy.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 proxy.FieldFallbackMode:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field fallback_mode", values[i])
|
||||
} else if value.Valid {
|
||||
_m.FallbackMode = value.String
|
||||
}
|
||||
case proxy.FieldBackupProxyID:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field backup_proxy_id", values[i])
|
||||
} else if value.Valid {
|
||||
_m.BackupProxyID = new(int64)
|
||||
*_m.BackupProxyID = value.Int64
|
||||
}
|
||||
case proxy.FieldExpiryWarnDays:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field expiry_warn_days", values[i])
|
||||
} else if value.Valid {
|
||||
_m.ExpiryWarnDays = int(value.Int64)
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
@@ -174,6 +221,11 @@ func (_m *Proxy) QueryAccounts() *AccountQuery {
|
||||
return NewProxyClient(_m.config).QueryAccounts(_m)
|
||||
}
|
||||
|
||||
// QueryBackupProxy queries the "backup_proxy" edge of the Proxy entity.
|
||||
func (_m *Proxy) QueryBackupProxy() *ProxyQuery {
|
||||
return NewProxyClient(_m.config).QueryBackupProxy(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this Proxy.
|
||||
// Note that you need to call Proxy.Unwrap() before calling this method if this Proxy
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
@@ -232,6 +284,22 @@ func (_m *Proxy) String() string {
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("status=")
|
||||
builder.WriteString(_m.Status)
|
||||
builder.WriteString(", ")
|
||||
if v := _m.ExpiresAt; v != nil {
|
||||
builder.WriteString("expires_at=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("fallback_mode=")
|
||||
builder.WriteString(_m.FallbackMode)
|
||||
builder.WriteString(", ")
|
||||
if v := _m.BackupProxyID; v != nil {
|
||||
builder.WriteString("backup_proxy_id=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("expiry_warn_days=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ExpiryWarnDays))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
@@ -35,8 +35,18 @@ const (
|
||||
FieldPassword = "password"
|
||||
// FieldStatus holds the string denoting the status field in the database.
|
||||
FieldStatus = "status"
|
||||
// FieldExpiresAt holds the string denoting the expires_at field in the database.
|
||||
FieldExpiresAt = "expires_at"
|
||||
// FieldFallbackMode holds the string denoting the fallback_mode field in the database.
|
||||
FieldFallbackMode = "fallback_mode"
|
||||
// FieldBackupProxyID holds the string denoting the backup_proxy_id field in the database.
|
||||
FieldBackupProxyID = "backup_proxy_id"
|
||||
// FieldExpiryWarnDays holds the string denoting the expiry_warn_days field in the database.
|
||||
FieldExpiryWarnDays = "expiry_warn_days"
|
||||
// EdgeAccounts holds the string denoting the accounts edge name in mutations.
|
||||
EdgeAccounts = "accounts"
|
||||
// EdgeBackupProxy holds the string denoting the backup_proxy edge name in mutations.
|
||||
EdgeBackupProxy = "backup_proxy"
|
||||
// Table holds the table name of the proxy in the database.
|
||||
Table = "proxies"
|
||||
// AccountsTable is the table that holds the accounts relation/edge.
|
||||
@@ -46,6 +56,10 @@ const (
|
||||
AccountsInverseTable = "accounts"
|
||||
// AccountsColumn is the table column denoting the accounts relation/edge.
|
||||
AccountsColumn = "proxy_id"
|
||||
// BackupProxyTable is the table that holds the backup_proxy relation/edge.
|
||||
BackupProxyTable = "proxies"
|
||||
// BackupProxyColumn is the table column denoting the backup_proxy relation/edge.
|
||||
BackupProxyColumn = "backup_proxy_id"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for proxy fields.
|
||||
@@ -61,6 +75,10 @@ var Columns = []string{
|
||||
FieldUsername,
|
||||
FieldPassword,
|
||||
FieldStatus,
|
||||
FieldExpiresAt,
|
||||
FieldFallbackMode,
|
||||
FieldBackupProxyID,
|
||||
FieldExpiryWarnDays,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
@@ -101,6 +119,12 @@ var (
|
||||
DefaultStatus string
|
||||
// StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||
StatusValidator func(string) error
|
||||
// DefaultFallbackMode holds the default value on creation for the "fallback_mode" field.
|
||||
DefaultFallbackMode string
|
||||
// FallbackModeValidator is a validator for the "fallback_mode" field. It is called by the builders before save.
|
||||
FallbackModeValidator func(string) error
|
||||
// DefaultExpiryWarnDays holds the default value on creation for the "expiry_warn_days" field.
|
||||
DefaultExpiryWarnDays int
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the Proxy queries.
|
||||
@@ -161,6 +185,26 @@ func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByExpiresAt orders the results by the expires_at field.
|
||||
func ByExpiresAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldExpiresAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByFallbackMode orders the results by the fallback_mode field.
|
||||
func ByFallbackMode(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldFallbackMode, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByBackupProxyID orders the results by the backup_proxy_id field.
|
||||
func ByBackupProxyID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldBackupProxyID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByExpiryWarnDays orders the results by the expiry_warn_days field.
|
||||
func ByExpiryWarnDays(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldExpiryWarnDays, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByAccountsCount orders the results by accounts count.
|
||||
func ByAccountsCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
@@ -174,6 +218,13 @@ func ByAccounts(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
sqlgraph.OrderByNeighborTerms(s, newAccountsStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByBackupProxyField orders the results by backup_proxy field.
|
||||
func ByBackupProxyField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newBackupProxyStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
func newAccountsStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
@@ -181,3 +232,10 @@ func newAccountsStep() *sqlgraph.Step {
|
||||
sqlgraph.Edge(sqlgraph.O2M, true, AccountsTable, AccountsColumn),
|
||||
)
|
||||
}
|
||||
func newBackupProxyStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2O, false, BackupProxyTable, BackupProxyColumn),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,6 +105,26 @@ func Status(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// ExpiresAt applies equality check predicate on the "expires_at" field. It's identical to ExpiresAtEQ.
|
||||
func ExpiresAt(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// FallbackMode applies equality check predicate on the "fallback_mode" field. It's identical to FallbackModeEQ.
|
||||
func FallbackMode(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// BackupProxyID applies equality check predicate on the "backup_proxy_id" field. It's identical to BackupProxyIDEQ.
|
||||
func BackupProxyID(v int64) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldBackupProxyID, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDays applies equality check predicate on the "expiry_warn_days" field. It's identical to ExpiryWarnDaysEQ.
|
||||
func ExpiryWarnDays(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldCreatedAt, v))
|
||||
@@ -685,6 +705,191 @@ func StatusContainsFold(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldContainsFold(FieldStatus, v))
|
||||
}
|
||||
|
||||
// ExpiresAtEQ applies the EQ predicate on the "expires_at" field.
|
||||
func ExpiresAtEQ(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtNEQ applies the NEQ predicate on the "expires_at" field.
|
||||
func ExpiresAtNEQ(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNEQ(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtIn applies the In predicate on the "expires_at" field.
|
||||
func ExpiresAtIn(vs ...time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIn(FieldExpiresAt, vs...))
|
||||
}
|
||||
|
||||
// ExpiresAtNotIn applies the NotIn predicate on the "expires_at" field.
|
||||
func ExpiresAtNotIn(vs ...time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotIn(FieldExpiresAt, vs...))
|
||||
}
|
||||
|
||||
// ExpiresAtGT applies the GT predicate on the "expires_at" field.
|
||||
func ExpiresAtGT(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGT(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtGTE applies the GTE predicate on the "expires_at" field.
|
||||
func ExpiresAtGTE(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGTE(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtLT applies the LT predicate on the "expires_at" field.
|
||||
func ExpiresAtLT(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLT(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtLTE applies the LTE predicate on the "expires_at" field.
|
||||
func ExpiresAtLTE(v time.Time) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLTE(FieldExpiresAt, v))
|
||||
}
|
||||
|
||||
// ExpiresAtIsNil applies the IsNil predicate on the "expires_at" field.
|
||||
func ExpiresAtIsNil() predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIsNull(FieldExpiresAt))
|
||||
}
|
||||
|
||||
// ExpiresAtNotNil applies the NotNil predicate on the "expires_at" field.
|
||||
func ExpiresAtNotNil() predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotNull(FieldExpiresAt))
|
||||
}
|
||||
|
||||
// FallbackModeEQ applies the EQ predicate on the "fallback_mode" field.
|
||||
func FallbackModeEQ(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeNEQ applies the NEQ predicate on the "fallback_mode" field.
|
||||
func FallbackModeNEQ(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNEQ(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeIn applies the In predicate on the "fallback_mode" field.
|
||||
func FallbackModeIn(vs ...string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIn(FieldFallbackMode, vs...))
|
||||
}
|
||||
|
||||
// FallbackModeNotIn applies the NotIn predicate on the "fallback_mode" field.
|
||||
func FallbackModeNotIn(vs ...string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotIn(FieldFallbackMode, vs...))
|
||||
}
|
||||
|
||||
// FallbackModeGT applies the GT predicate on the "fallback_mode" field.
|
||||
func FallbackModeGT(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGT(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeGTE applies the GTE predicate on the "fallback_mode" field.
|
||||
func FallbackModeGTE(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGTE(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeLT applies the LT predicate on the "fallback_mode" field.
|
||||
func FallbackModeLT(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLT(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeLTE applies the LTE predicate on the "fallback_mode" field.
|
||||
func FallbackModeLTE(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLTE(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeContains applies the Contains predicate on the "fallback_mode" field.
|
||||
func FallbackModeContains(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldContains(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeHasPrefix applies the HasPrefix predicate on the "fallback_mode" field.
|
||||
func FallbackModeHasPrefix(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldHasPrefix(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeHasSuffix applies the HasSuffix predicate on the "fallback_mode" field.
|
||||
func FallbackModeHasSuffix(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldHasSuffix(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeEqualFold applies the EqualFold predicate on the "fallback_mode" field.
|
||||
func FallbackModeEqualFold(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEqualFold(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// FallbackModeContainsFold applies the ContainsFold predicate on the "fallback_mode" field.
|
||||
func FallbackModeContainsFold(v string) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldContainsFold(FieldFallbackMode, v))
|
||||
}
|
||||
|
||||
// BackupProxyIDEQ applies the EQ predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDEQ(v int64) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldBackupProxyID, v))
|
||||
}
|
||||
|
||||
// BackupProxyIDNEQ applies the NEQ predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDNEQ(v int64) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNEQ(FieldBackupProxyID, v))
|
||||
}
|
||||
|
||||
// BackupProxyIDIn applies the In predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDIn(vs ...int64) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIn(FieldBackupProxyID, vs...))
|
||||
}
|
||||
|
||||
// BackupProxyIDNotIn applies the NotIn predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDNotIn(vs ...int64) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotIn(FieldBackupProxyID, vs...))
|
||||
}
|
||||
|
||||
// BackupProxyIDIsNil applies the IsNil predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDIsNil() predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIsNull(FieldBackupProxyID))
|
||||
}
|
||||
|
||||
// BackupProxyIDNotNil applies the NotNil predicate on the "backup_proxy_id" field.
|
||||
func BackupProxyIDNotNil() predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotNull(FieldBackupProxyID))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysEQ applies the EQ predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysEQ(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldEQ(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysNEQ applies the NEQ predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysNEQ(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNEQ(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysIn applies the In predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysIn(vs ...int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldIn(FieldExpiryWarnDays, vs...))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysNotIn applies the NotIn predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysNotIn(vs ...int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldNotIn(FieldExpiryWarnDays, vs...))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysGT applies the GT predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysGT(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGT(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysGTE applies the GTE predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysGTE(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldGTE(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysLT applies the LT predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysLT(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLT(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// ExpiryWarnDaysLTE applies the LTE predicate on the "expiry_warn_days" field.
|
||||
func ExpiryWarnDaysLTE(v int) predicate.Proxy {
|
||||
return predicate.Proxy(sql.FieldLTE(FieldExpiryWarnDays, v))
|
||||
}
|
||||
|
||||
// HasAccounts applies the HasEdge predicate on the "accounts" edge.
|
||||
func HasAccounts() predicate.Proxy {
|
||||
return predicate.Proxy(func(s *sql.Selector) {
|
||||
@@ -708,6 +913,29 @@ func HasAccountsWith(preds ...predicate.Account) predicate.Proxy {
|
||||
})
|
||||
}
|
||||
|
||||
// HasBackupProxy applies the HasEdge predicate on the "backup_proxy" edge.
|
||||
func HasBackupProxy() predicate.Proxy {
|
||||
return predicate.Proxy(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2O, false, BackupProxyTable, BackupProxyColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasBackupProxyWith applies the HasEdge predicate on the "backup_proxy" edge with a given conditions (other predicates).
|
||||
func HasBackupProxyWith(preds ...predicate.Proxy) predicate.Proxy {
|
||||
return predicate.Proxy(func(s *sql.Selector) {
|
||||
step := newBackupProxyStep()
|
||||
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
|
||||
for _, p := range preds {
|
||||
p(s)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// And groups predicates with the AND operator between them.
|
||||
func And(predicates ...predicate.Proxy) predicate.Proxy {
|
||||
return predicate.Proxy(sql.AndPredicates(predicates...))
|
||||
|
||||
@@ -131,6 +131,62 @@ func (_c *ProxyCreate) SetNillableStatus(v *string) *ProxyCreate {
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (_c *ProxyCreate) SetExpiresAt(v time.Time) *ProxyCreate {
|
||||
_c.mutation.SetExpiresAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (_c *ProxyCreate) SetNillableExpiresAt(v *time.Time) *ProxyCreate {
|
||||
if v != nil {
|
||||
_c.SetExpiresAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (_c *ProxyCreate) SetFallbackMode(v string) *ProxyCreate {
|
||||
_c.mutation.SetFallbackMode(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableFallbackMode sets the "fallback_mode" field if the given value is not nil.
|
||||
func (_c *ProxyCreate) SetNillableFallbackMode(v *string) *ProxyCreate {
|
||||
if v != nil {
|
||||
_c.SetFallbackMode(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (_c *ProxyCreate) SetBackupProxyID(v int64) *ProxyCreate {
|
||||
_c.mutation.SetBackupProxyID(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableBackupProxyID sets the "backup_proxy_id" field if the given value is not nil.
|
||||
func (_c *ProxyCreate) SetNillableBackupProxyID(v *int64) *ProxyCreate {
|
||||
if v != nil {
|
||||
_c.SetBackupProxyID(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (_c *ProxyCreate) SetExpiryWarnDays(v int) *ProxyCreate {
|
||||
_c.mutation.SetExpiryWarnDays(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableExpiryWarnDays sets the "expiry_warn_days" field if the given value is not nil.
|
||||
func (_c *ProxyCreate) SetNillableExpiryWarnDays(v *int) *ProxyCreate {
|
||||
if v != nil {
|
||||
_c.SetExpiryWarnDays(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
|
||||
func (_c *ProxyCreate) AddAccountIDs(ids ...int64) *ProxyCreate {
|
||||
_c.mutation.AddAccountIDs(ids...)
|
||||
@@ -146,6 +202,11 @@ func (_c *ProxyCreate) AddAccounts(v ...*Account) *ProxyCreate {
|
||||
return _c.AddAccountIDs(ids...)
|
||||
}
|
||||
|
||||
// SetBackupProxy sets the "backup_proxy" edge to the Proxy entity.
|
||||
func (_c *ProxyCreate) SetBackupProxy(v *Proxy) *ProxyCreate {
|
||||
return _c.SetBackupProxyID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ProxyMutation object of the builder.
|
||||
func (_c *ProxyCreate) Mutation() *ProxyMutation {
|
||||
return _c.mutation
|
||||
@@ -201,6 +262,14 @@ func (_c *ProxyCreate) defaults() error {
|
||||
v := proxy.DefaultStatus
|
||||
_c.mutation.SetStatus(v)
|
||||
}
|
||||
if _, ok := _c.mutation.FallbackMode(); !ok {
|
||||
v := proxy.DefaultFallbackMode
|
||||
_c.mutation.SetFallbackMode(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ExpiryWarnDays(); !ok {
|
||||
v := proxy.DefaultExpiryWarnDays
|
||||
_c.mutation.SetExpiryWarnDays(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -257,6 +326,17 @@ func (_c *ProxyCreate) check() error {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Proxy.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.FallbackMode(); !ok {
|
||||
return &ValidationError{Name: "fallback_mode", err: errors.New(`ent: missing required field "Proxy.fallback_mode"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.FallbackMode(); ok {
|
||||
if err := proxy.FallbackModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "fallback_mode", err: fmt.Errorf(`ent: validator failed for field "Proxy.fallback_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.ExpiryWarnDays(); !ok {
|
||||
return &ValidationError{Name: "expiry_warn_days", err: errors.New(`ent: missing required field "Proxy.expiry_warn_days"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -324,6 +404,18 @@ func (_c *ProxyCreate) createSpec() (*Proxy, *sqlgraph.CreateSpec) {
|
||||
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
|
||||
_node.Status = value
|
||||
}
|
||||
if value, ok := _c.mutation.ExpiresAt(); ok {
|
||||
_spec.SetField(proxy.FieldExpiresAt, field.TypeTime, value)
|
||||
_node.ExpiresAt = &value
|
||||
}
|
||||
if value, ok := _c.mutation.FallbackMode(); ok {
|
||||
_spec.SetField(proxy.FieldFallbackMode, field.TypeString, value)
|
||||
_node.FallbackMode = value
|
||||
}
|
||||
if value, ok := _c.mutation.ExpiryWarnDays(); ok {
|
||||
_spec.SetField(proxy.FieldExpiryWarnDays, field.TypeInt, value)
|
||||
_node.ExpiryWarnDays = value
|
||||
}
|
||||
if nodes := _c.mutation.AccountsIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
@@ -340,6 +432,23 @@ func (_c *ProxyCreate) createSpec() (*Proxy, *sqlgraph.CreateSpec) {
|
||||
}
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
if nodes := _c.mutation.BackupProxyIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2O,
|
||||
Inverse: false,
|
||||
Table: proxy.BackupProxyTable,
|
||||
Columns: []string{proxy.BackupProxyColumn},
|
||||
Bidi: true,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(proxy.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.BackupProxyID = &nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
@@ -524,6 +633,72 @@ func (u *ProxyUpsert) UpdateStatus() *ProxyUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (u *ProxyUpsert) SetExpiresAt(v time.Time) *ProxyUpsert {
|
||||
u.Set(proxy.FieldExpiresAt, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
|
||||
func (u *ProxyUpsert) UpdateExpiresAt() *ProxyUpsert {
|
||||
u.SetExcluded(proxy.FieldExpiresAt)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearExpiresAt clears the value of the "expires_at" field.
|
||||
func (u *ProxyUpsert) ClearExpiresAt() *ProxyUpsert {
|
||||
u.SetNull(proxy.FieldExpiresAt)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (u *ProxyUpsert) SetFallbackMode(v string) *ProxyUpsert {
|
||||
u.Set(proxy.FieldFallbackMode, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateFallbackMode sets the "fallback_mode" field to the value that was provided on create.
|
||||
func (u *ProxyUpsert) UpdateFallbackMode() *ProxyUpsert {
|
||||
u.SetExcluded(proxy.FieldFallbackMode)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsert) SetBackupProxyID(v int64) *ProxyUpsert {
|
||||
u.Set(proxy.FieldBackupProxyID, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateBackupProxyID sets the "backup_proxy_id" field to the value that was provided on create.
|
||||
func (u *ProxyUpsert) UpdateBackupProxyID() *ProxyUpsert {
|
||||
u.SetExcluded(proxy.FieldBackupProxyID)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsert) ClearBackupProxyID() *ProxyUpsert {
|
||||
u.SetNull(proxy.FieldBackupProxyID)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsert) SetExpiryWarnDays(v int) *ProxyUpsert {
|
||||
u.Set(proxy.FieldExpiryWarnDays, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateExpiryWarnDays sets the "expiry_warn_days" field to the value that was provided on create.
|
||||
func (u *ProxyUpsert) UpdateExpiryWarnDays() *ProxyUpsert {
|
||||
u.SetExcluded(proxy.FieldExpiryWarnDays)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds v to the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsert) AddExpiryWarnDays(v int) *ProxyUpsert {
|
||||
u.Add(proxy.FieldExpiryWarnDays, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
@@ -723,6 +898,83 @@ func (u *ProxyUpsertOne) UpdateStatus() *ProxyUpsertOne {
|
||||
})
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (u *ProxyUpsertOne) SetExpiresAt(v time.Time) *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetExpiresAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertOne) UpdateExpiresAt() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateExpiresAt()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExpiresAt clears the value of the "expires_at" field.
|
||||
func (u *ProxyUpsertOne) ClearExpiresAt() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.ClearExpiresAt()
|
||||
})
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (u *ProxyUpsertOne) SetFallbackMode(v string) *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetFallbackMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFallbackMode sets the "fallback_mode" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertOne) UpdateFallbackMode() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateFallbackMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsertOne) SetBackupProxyID(v int64) *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetBackupProxyID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBackupProxyID sets the "backup_proxy_id" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertOne) UpdateBackupProxyID() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateBackupProxyID()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsertOne) ClearBackupProxyID() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.ClearBackupProxyID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsertOne) SetExpiryWarnDays(v int) *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetExpiryWarnDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds v to the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsertOne) AddExpiryWarnDays(v int) *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.AddExpiryWarnDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExpiryWarnDays sets the "expiry_warn_days" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertOne) UpdateExpiryWarnDays() *ProxyUpsertOne {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateExpiryWarnDays()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *ProxyUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
@@ -1088,6 +1340,83 @@ func (u *ProxyUpsertBulk) UpdateStatus() *ProxyUpsertBulk {
|
||||
})
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (u *ProxyUpsertBulk) SetExpiresAt(v time.Time) *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetExpiresAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExpiresAt sets the "expires_at" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertBulk) UpdateExpiresAt() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateExpiresAt()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExpiresAt clears the value of the "expires_at" field.
|
||||
func (u *ProxyUpsertBulk) ClearExpiresAt() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.ClearExpiresAt()
|
||||
})
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (u *ProxyUpsertBulk) SetFallbackMode(v string) *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetFallbackMode(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateFallbackMode sets the "fallback_mode" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertBulk) UpdateFallbackMode() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateFallbackMode()
|
||||
})
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsertBulk) SetBackupProxyID(v int64) *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetBackupProxyID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBackupProxyID sets the "backup_proxy_id" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertBulk) UpdateBackupProxyID() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateBackupProxyID()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (u *ProxyUpsertBulk) ClearBackupProxyID() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.ClearBackupProxyID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsertBulk) SetExpiryWarnDays(v int) *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.SetExpiryWarnDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds v to the "expiry_warn_days" field.
|
||||
func (u *ProxyUpsertBulk) AddExpiryWarnDays(v int) *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.AddExpiryWarnDays(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateExpiryWarnDays sets the "expiry_warn_days" field to the value that was provided on create.
|
||||
func (u *ProxyUpsertBulk) UpdateExpiryWarnDays() *ProxyUpsertBulk {
|
||||
return u.Update(func(s *ProxyUpsert) {
|
||||
s.UpdateExpiryWarnDays()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *ProxyUpsertBulk) Exec(ctx context.Context) error {
|
||||
if u.create.err != nil {
|
||||
|
||||
+90
-13
@@ -21,12 +21,13 @@ import (
|
||||
// ProxyQuery is the builder for querying Proxy entities.
|
||||
type ProxyQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []proxy.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.Proxy
|
||||
withAccounts *AccountQuery
|
||||
modifiers []func(*sql.Selector)
|
||||
ctx *QueryContext
|
||||
order []proxy.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.Proxy
|
||||
withAccounts *AccountQuery
|
||||
withBackupProxy *ProxyQuery
|
||||
modifiers []func(*sql.Selector)
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
@@ -85,6 +86,28 @@ func (_q *ProxyQuery) QueryAccounts() *AccountQuery {
|
||||
return query
|
||||
}
|
||||
|
||||
// QueryBackupProxy chains the current query on the "backup_proxy" edge.
|
||||
func (_q *ProxyQuery) QueryBackupProxy() *ProxyQuery {
|
||||
query := (&ProxyClient{config: _q.config}).Query()
|
||||
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selector := _q.sqlQuery(ctx)
|
||||
if err := selector.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(proxy.Table, proxy.FieldID, selector),
|
||||
sqlgraph.To(proxy.Table, proxy.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2O, false, proxy.BackupProxyTable, proxy.BackupProxyColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first Proxy entity from the query.
|
||||
// Returns a *NotFoundError when no Proxy was found.
|
||||
func (_q *ProxyQuery) First(ctx context.Context) (*Proxy, error) {
|
||||
@@ -272,12 +295,13 @@ func (_q *ProxyQuery) Clone() *ProxyQuery {
|
||||
return nil
|
||||
}
|
||||
return &ProxyQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]proxy.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.Proxy{}, _q.predicates...),
|
||||
withAccounts: _q.withAccounts.Clone(),
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]proxy.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.Proxy{}, _q.predicates...),
|
||||
withAccounts: _q.withAccounts.Clone(),
|
||||
withBackupProxy: _q.withBackupProxy.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
@@ -295,6 +319,17 @@ func (_q *ProxyQuery) WithAccounts(opts ...func(*AccountQuery)) *ProxyQuery {
|
||||
return _q
|
||||
}
|
||||
|
||||
// WithBackupProxy tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "backup_proxy" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *ProxyQuery) WithBackupProxy(opts ...func(*ProxyQuery)) *ProxyQuery {
|
||||
query := (&ProxyClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withBackupProxy = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
@@ -373,8 +408,9 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
|
||||
var (
|
||||
nodes = []*Proxy{}
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [1]bool{
|
||||
loadedTypes = [2]bool{
|
||||
_q.withAccounts != nil,
|
||||
_q.withBackupProxy != nil,
|
||||
}
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
@@ -405,6 +441,12 @@ func (_q *ProxyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Proxy,
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if query := _q.withBackupProxy; query != nil {
|
||||
if err := _q.loadBackupProxy(ctx, query, nodes, nil,
|
||||
func(n *Proxy, e *Proxy) { n.Edges.BackupProxy = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
@@ -441,6 +483,38 @@ func (_q *ProxyQuery) loadAccounts(ctx context.Context, query *AccountQuery, nod
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (_q *ProxyQuery) loadBackupProxy(ctx context.Context, query *ProxyQuery, nodes []*Proxy, init func(*Proxy), assign func(*Proxy, *Proxy)) error {
|
||||
ids := make([]int64, 0, len(nodes))
|
||||
nodeids := make(map[int64][]*Proxy)
|
||||
for i := range nodes {
|
||||
if nodes[i].BackupProxyID == nil {
|
||||
continue
|
||||
}
|
||||
fk := *nodes[i].BackupProxyID
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
query.Where(proxy.IDIn(ids...))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
nodes, ok := nodeids[n.ID]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected foreign-key "backup_proxy_id" returned %v`, n.ID)
|
||||
}
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ProxyQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
@@ -470,6 +544,9 @@ func (_q *ProxyQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
if _q.withBackupProxy != nil {
|
||||
_spec.Node.AddColumnOnce(proxy.FieldBackupProxyID)
|
||||
}
|
||||
}
|
||||
if ps := _q.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
|
||||
@@ -172,6 +172,81 @@ func (_u *ProxyUpdate) SetNillableStatus(v *string) *ProxyUpdate {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (_u *ProxyUpdate) SetExpiresAt(v time.Time) *ProxyUpdate {
|
||||
_u.mutation.SetExpiresAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (_u *ProxyUpdate) SetNillableExpiresAt(v *time.Time) *ProxyUpdate {
|
||||
if v != nil {
|
||||
_u.SetExpiresAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearExpiresAt clears the value of the "expires_at" field.
|
||||
func (_u *ProxyUpdate) ClearExpiresAt() *ProxyUpdate {
|
||||
_u.mutation.ClearExpiresAt()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (_u *ProxyUpdate) SetFallbackMode(v string) *ProxyUpdate {
|
||||
_u.mutation.SetFallbackMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableFallbackMode sets the "fallback_mode" field if the given value is not nil.
|
||||
func (_u *ProxyUpdate) SetNillableFallbackMode(v *string) *ProxyUpdate {
|
||||
if v != nil {
|
||||
_u.SetFallbackMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (_u *ProxyUpdate) SetBackupProxyID(v int64) *ProxyUpdate {
|
||||
_u.mutation.SetBackupProxyID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableBackupProxyID sets the "backup_proxy_id" field if the given value is not nil.
|
||||
func (_u *ProxyUpdate) SetNillableBackupProxyID(v *int64) *ProxyUpdate {
|
||||
if v != nil {
|
||||
_u.SetBackupProxyID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (_u *ProxyUpdate) ClearBackupProxyID() *ProxyUpdate {
|
||||
_u.mutation.ClearBackupProxyID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (_u *ProxyUpdate) SetExpiryWarnDays(v int) *ProxyUpdate {
|
||||
_u.mutation.ResetExpiryWarnDays()
|
||||
_u.mutation.SetExpiryWarnDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiryWarnDays sets the "expiry_warn_days" field if the given value is not nil.
|
||||
func (_u *ProxyUpdate) SetNillableExpiryWarnDays(v *int) *ProxyUpdate {
|
||||
if v != nil {
|
||||
_u.SetExpiryWarnDays(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds value to the "expiry_warn_days" field.
|
||||
func (_u *ProxyUpdate) AddExpiryWarnDays(v int) *ProxyUpdate {
|
||||
_u.mutation.AddExpiryWarnDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
|
||||
func (_u *ProxyUpdate) AddAccountIDs(ids ...int64) *ProxyUpdate {
|
||||
_u.mutation.AddAccountIDs(ids...)
|
||||
@@ -187,6 +262,11 @@ func (_u *ProxyUpdate) AddAccounts(v ...*Account) *ProxyUpdate {
|
||||
return _u.AddAccountIDs(ids...)
|
||||
}
|
||||
|
||||
// SetBackupProxy sets the "backup_proxy" edge to the Proxy entity.
|
||||
func (_u *ProxyUpdate) SetBackupProxy(v *Proxy) *ProxyUpdate {
|
||||
return _u.SetBackupProxyID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ProxyMutation object of the builder.
|
||||
func (_u *ProxyUpdate) Mutation() *ProxyMutation {
|
||||
return _u.mutation
|
||||
@@ -213,6 +293,12 @@ func (_u *ProxyUpdate) RemoveAccounts(v ...*Account) *ProxyUpdate {
|
||||
return _u.RemoveAccountIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearBackupProxy clears the "backup_proxy" edge to the Proxy entity.
|
||||
func (_u *ProxyUpdate) ClearBackupProxy() *ProxyUpdate {
|
||||
_u.mutation.ClearBackupProxy()
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *ProxyUpdate) Save(ctx context.Context) (int, error) {
|
||||
if err := _u.defaults(); err != nil {
|
||||
@@ -287,6 +373,11 @@ func (_u *ProxyUpdate) check() error {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Proxy.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.FallbackMode(); ok {
|
||||
if err := proxy.FallbackModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "fallback_mode", err: fmt.Errorf(`ent: validator failed for field "Proxy.fallback_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -341,6 +432,21 @@ func (_u *ProxyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiresAt(); ok {
|
||||
_spec.SetField(proxy.FieldExpiresAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.ExpiresAtCleared() {
|
||||
_spec.ClearField(proxy.FieldExpiresAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.FallbackMode(); ok {
|
||||
_spec.SetField(proxy.FieldFallbackMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiryWarnDays(); ok {
|
||||
_spec.SetField(proxy.FieldExpiryWarnDays, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedExpiryWarnDays(); ok {
|
||||
_spec.AddField(proxy.FieldExpiryWarnDays, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.AccountsCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
@@ -386,6 +492,35 @@ func (_u *ProxyUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _u.mutation.BackupProxyCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2O,
|
||||
Inverse: false,
|
||||
Table: proxy.BackupProxyTable,
|
||||
Columns: []string{proxy.BackupProxyColumn},
|
||||
Bidi: true,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(proxy.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.BackupProxyIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2O,
|
||||
Inverse: false,
|
||||
Table: proxy.BackupProxyTable,
|
||||
Columns: []string{proxy.BackupProxyColumn},
|
||||
Bidi: true,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(proxy.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{proxy.Label}
|
||||
@@ -549,6 +684,81 @@ func (_u *ProxyUpdateOne) SetNillableStatus(v *string) *ProxyUpdateOne {
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiresAt sets the "expires_at" field.
|
||||
func (_u *ProxyUpdateOne) SetExpiresAt(v time.Time) *ProxyUpdateOne {
|
||||
_u.mutation.SetExpiresAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiresAt sets the "expires_at" field if the given value is not nil.
|
||||
func (_u *ProxyUpdateOne) SetNillableExpiresAt(v *time.Time) *ProxyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetExpiresAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearExpiresAt clears the value of the "expires_at" field.
|
||||
func (_u *ProxyUpdateOne) ClearExpiresAt() *ProxyUpdateOne {
|
||||
_u.mutation.ClearExpiresAt()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetFallbackMode sets the "fallback_mode" field.
|
||||
func (_u *ProxyUpdateOne) SetFallbackMode(v string) *ProxyUpdateOne {
|
||||
_u.mutation.SetFallbackMode(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableFallbackMode sets the "fallback_mode" field if the given value is not nil.
|
||||
func (_u *ProxyUpdateOne) SetNillableFallbackMode(v *string) *ProxyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetFallbackMode(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetBackupProxyID sets the "backup_proxy_id" field.
|
||||
func (_u *ProxyUpdateOne) SetBackupProxyID(v int64) *ProxyUpdateOne {
|
||||
_u.mutation.SetBackupProxyID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableBackupProxyID sets the "backup_proxy_id" field if the given value is not nil.
|
||||
func (_u *ProxyUpdateOne) SetNillableBackupProxyID(v *int64) *ProxyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetBackupProxyID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearBackupProxyID clears the value of the "backup_proxy_id" field.
|
||||
func (_u *ProxyUpdateOne) ClearBackupProxyID() *ProxyUpdateOne {
|
||||
_u.mutation.ClearBackupProxyID()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExpiryWarnDays sets the "expiry_warn_days" field.
|
||||
func (_u *ProxyUpdateOne) SetExpiryWarnDays(v int) *ProxyUpdateOne {
|
||||
_u.mutation.ResetExpiryWarnDays()
|
||||
_u.mutation.SetExpiryWarnDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableExpiryWarnDays sets the "expiry_warn_days" field if the given value is not nil.
|
||||
func (_u *ProxyUpdateOne) SetNillableExpiryWarnDays(v *int) *ProxyUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetExpiryWarnDays(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddExpiryWarnDays adds value to the "expiry_warn_days" field.
|
||||
func (_u *ProxyUpdateOne) AddExpiryWarnDays(v int) *ProxyUpdateOne {
|
||||
_u.mutation.AddExpiryWarnDays(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddAccountIDs adds the "accounts" edge to the Account entity by IDs.
|
||||
func (_u *ProxyUpdateOne) AddAccountIDs(ids ...int64) *ProxyUpdateOne {
|
||||
_u.mutation.AddAccountIDs(ids...)
|
||||
@@ -564,6 +774,11 @@ func (_u *ProxyUpdateOne) AddAccounts(v ...*Account) *ProxyUpdateOne {
|
||||
return _u.AddAccountIDs(ids...)
|
||||
}
|
||||
|
||||
// SetBackupProxy sets the "backup_proxy" edge to the Proxy entity.
|
||||
func (_u *ProxyUpdateOne) SetBackupProxy(v *Proxy) *ProxyUpdateOne {
|
||||
return _u.SetBackupProxyID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ProxyMutation object of the builder.
|
||||
func (_u *ProxyUpdateOne) Mutation() *ProxyMutation {
|
||||
return _u.mutation
|
||||
@@ -590,6 +805,12 @@ func (_u *ProxyUpdateOne) RemoveAccounts(v ...*Account) *ProxyUpdateOne {
|
||||
return _u.RemoveAccountIDs(ids...)
|
||||
}
|
||||
|
||||
// ClearBackupProxy clears the "backup_proxy" edge to the Proxy entity.
|
||||
func (_u *ProxyUpdateOne) ClearBackupProxy() *ProxyUpdateOne {
|
||||
_u.mutation.ClearBackupProxy()
|
||||
return _u
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ProxyUpdate builder.
|
||||
func (_u *ProxyUpdateOne) Where(ps ...predicate.Proxy) *ProxyUpdateOne {
|
||||
_u.mutation.Where(ps...)
|
||||
@@ -677,6 +898,11 @@ func (_u *ProxyUpdateOne) check() error {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Proxy.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.FallbackMode(); ok {
|
||||
if err := proxy.FallbackModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "fallback_mode", err: fmt.Errorf(`ent: validator failed for field "Proxy.fallback_mode": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -748,6 +974,21 @@ func (_u *ProxyUpdateOne) sqlSave(ctx context.Context) (_node *Proxy, err error)
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(proxy.FieldStatus, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiresAt(); ok {
|
||||
_spec.SetField(proxy.FieldExpiresAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.ExpiresAtCleared() {
|
||||
_spec.ClearField(proxy.FieldExpiresAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.FallbackMode(); ok {
|
||||
_spec.SetField(proxy.FieldFallbackMode, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExpiryWarnDays(); ok {
|
||||
_spec.SetField(proxy.FieldExpiryWarnDays, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedExpiryWarnDays(); ok {
|
||||
_spec.AddField(proxy.FieldExpiryWarnDays, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.AccountsCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
@@ -793,6 +1034,35 @@ func (_u *ProxyUpdateOne) sqlSave(ctx context.Context) (_node *Proxy, err error)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
if _u.mutation.BackupProxyCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2O,
|
||||
Inverse: false,
|
||||
Table: proxy.BackupProxyTable,
|
||||
Columns: []string{proxy.BackupProxyColumn},
|
||||
Bidi: true,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(proxy.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.BackupProxyIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2O,
|
||||
Inverse: false,
|
||||
Table: proxy.BackupProxyTable,
|
||||
Columns: []string{proxy.BackupProxyColumn},
|
||||
Bidi: true,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(proxy.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
_node = &Proxy{config: _u.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
|
||||
@@ -223,33 +223,33 @@ func init() {
|
||||
// account.DefaultExtra holds the default value on creation for the extra field.
|
||||
account.DefaultExtra = accountDescExtra.Default.(func() map[string]interface{})
|
||||
// accountDescConcurrency is the schema descriptor for concurrency field.
|
||||
accountDescConcurrency := accountFields[7].Descriptor()
|
||||
accountDescConcurrency := accountFields[8].Descriptor()
|
||||
// account.DefaultConcurrency holds the default value on creation for the concurrency field.
|
||||
account.DefaultConcurrency = accountDescConcurrency.Default.(int)
|
||||
// accountDescPriority is the schema descriptor for priority field.
|
||||
accountDescPriority := accountFields[9].Descriptor()
|
||||
accountDescPriority := accountFields[10].Descriptor()
|
||||
// account.DefaultPriority holds the default value on creation for the priority field.
|
||||
account.DefaultPriority = accountDescPriority.Default.(int)
|
||||
// accountDescRateMultiplier is the schema descriptor for rate_multiplier field.
|
||||
accountDescRateMultiplier := accountFields[10].Descriptor()
|
||||
accountDescRateMultiplier := accountFields[11].Descriptor()
|
||||
// account.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field.
|
||||
account.DefaultRateMultiplier = accountDescRateMultiplier.Default.(float64)
|
||||
// accountDescStatus is the schema descriptor for status field.
|
||||
accountDescStatus := accountFields[11].Descriptor()
|
||||
accountDescStatus := accountFields[12].Descriptor()
|
||||
// account.DefaultStatus holds the default value on creation for the status field.
|
||||
account.DefaultStatus = accountDescStatus.Default.(string)
|
||||
// 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)
|
||||
// accountDescAutoPauseOnExpired is the schema descriptor for auto_pause_on_expired field.
|
||||
accountDescAutoPauseOnExpired := accountFields[15].Descriptor()
|
||||
accountDescAutoPauseOnExpired := accountFields[16].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 := accountFields[16].Descriptor()
|
||||
accountDescSchedulable := accountFields[17].Descriptor()
|
||||
// account.DefaultSchedulable holds the default value on creation for the schedulable field.
|
||||
account.DefaultSchedulable = accountDescSchedulable.Default.(bool)
|
||||
// accountDescSessionWindowStatus is the schema descriptor for session_window_status field.
|
||||
accountDescSessionWindowStatus := accountFields[24].Descriptor()
|
||||
accountDescSessionWindowStatus := accountFields[25].Descriptor()
|
||||
// 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)
|
||||
accountgroupFields := schema.AccountGroup{}.Fields()
|
||||
@@ -1362,6 +1362,16 @@ func init() {
|
||||
proxy.DefaultStatus = proxyDescStatus.Default.(string)
|
||||
// proxy.StatusValidator is a validator for the "status" field. It is called by the builders before save.
|
||||
proxy.StatusValidator = proxyDescStatus.Validators[0].(func(string) error)
|
||||
// proxyDescFallbackMode is the schema descriptor for fallback_mode field.
|
||||
proxyDescFallbackMode := proxyFields[8].Descriptor()
|
||||
// proxy.DefaultFallbackMode holds the default value on creation for the fallback_mode field.
|
||||
proxy.DefaultFallbackMode = proxyDescFallbackMode.Default.(string)
|
||||
// proxy.FallbackModeValidator is a validator for the "fallback_mode" field. It is called by the builders before save.
|
||||
proxy.FallbackModeValidator = proxyDescFallbackMode.Validators[0].(func(string) error)
|
||||
// proxyDescExpiryWarnDays is the schema descriptor for expiry_warn_days field.
|
||||
proxyDescExpiryWarnDays := proxyFields[10].Descriptor()
|
||||
// proxy.DefaultExpiryWarnDays holds the default value on creation for the expiry_warn_days field.
|
||||
proxy.DefaultExpiryWarnDays = proxyDescExpiryWarnDays.Default.(int)
|
||||
redeemcodeFields := schema.RedeemCode{}.Fields()
|
||||
_ = redeemcodeFields
|
||||
// redeemcodeDescCode is the schema descriptor for code field.
|
||||
|
||||
@@ -91,6 +91,9 @@ func (Account) Fields() []ent.Field {
|
||||
field.Int64("proxy_id").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.Int64("proxy_fallback_origin_id").
|
||||
Optional().Nillable().
|
||||
Comment("Original proxy id replaced by expiry-fallback; for manual revert. NULL = not in fallback."),
|
||||
|
||||
// concurrency: 账户最大并发请求数
|
||||
// 用于限制同一时间对该账户发起的请求数量
|
||||
|
||||
@@ -52,6 +52,18 @@ func (Proxy) Fields() []ent.Field {
|
||||
field.String("status").
|
||||
MaxLen(20).
|
||||
Default("active"),
|
||||
field.Time("expires_at").
|
||||
Optional().Nillable().
|
||||
Comment("Proxy expiration time (NULL means never expires)."),
|
||||
field.String("fallback_mode").
|
||||
MaxLen(20).Default("none").
|
||||
Comment("Fallback target on expiry: none | proxy | direct."),
|
||||
field.Int64("backup_proxy_id").
|
||||
Optional().Nillable().
|
||||
Comment("Backup proxy id when fallback_mode=proxy (self-reference)."),
|
||||
field.Int("expiry_warn_days").
|
||||
Default(7).
|
||||
Comment("Days before expiry to flag as expiring-soon (per proxy)."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +73,9 @@ func (Proxy) Edges() []ent.Edge {
|
||||
// accounts: 使用此代理的账户(反向边)
|
||||
edge.From("accounts", Account.Type).
|
||||
Ref("proxy"),
|
||||
edge.To("backup_proxy", Proxy.Type).
|
||||
Field("backup_proxy_id").
|
||||
Unique(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,5 +83,7 @@ func (Proxy) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("status"),
|
||||
index.Fields("deleted_at"),
|
||||
index.Fields("expires_at"),
|
||||
index.Fields("backup_proxy_id"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,18 @@ type DataPayload struct {
|
||||
}
|
||||
|
||||
type DataProxy struct {
|
||||
ProxyKey string `json:"proxy_key"`
|
||||
Name string `json:"name"`
|
||||
Protocol string `json:"protocol"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Status string `json:"status"`
|
||||
ProxyKey string `json:"proxy_key"`
|
||||
Name string `json:"name"`
|
||||
Protocol string `json:"protocol"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Status string `json:"status"`
|
||||
ExpiresAt *int64 `json:"expires_at,omitempty"` // unix 秒,与 DataAccount.ExpiresAt 风格一致
|
||||
FallbackMode string `json:"fallback_mode,omitempty"` // none/direct/proxy
|
||||
BackupProxyName string `json:"backup_proxy_name,omitempty"` // 备用代理 name(跨实例按 name 反查)
|
||||
ExpiryWarnDays int `json:"expiry_warn_days,omitempty"`
|
||||
}
|
||||
|
||||
// DataAccount 是管理员显式备份导出使用的账号结构,故意不走 dto.Account 的脱敏路径,
|
||||
@@ -118,21 +122,41 @@ func (h *AccountHandler) ExportData(c *gin.Context) {
|
||||
proxies = []service.Proxy{}
|
||||
}
|
||||
|
||||
// 构建 id→name 映射,用于导出备用代理 name
|
||||
proxyNameByID := make(map[int64]string, len(proxies))
|
||||
for i := range proxies {
|
||||
proxyNameByID[proxies[i].ID] = proxies[i].Name
|
||||
}
|
||||
|
||||
proxyKeyByID := make(map[int64]string, len(proxies))
|
||||
dataProxies := make([]DataProxy, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
p := proxies[i]
|
||||
key := buildProxyKey(p.Protocol, p.Host, p.Port, p.Username, p.Password)
|
||||
proxyKeyByID[p.ID] = key
|
||||
|
||||
var expiresAt *int64
|
||||
if p.ExpiresAt != nil {
|
||||
v := p.ExpiresAt.Unix()
|
||||
expiresAt = &v
|
||||
}
|
||||
var backupProxyName string
|
||||
if p.BackupProxyID != nil {
|
||||
backupProxyName = proxyNameByID[*p.BackupProxyID]
|
||||
}
|
||||
dataProxies = append(dataProxies, DataProxy{
|
||||
ProxyKey: key,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
Status: p.Status,
|
||||
ProxyKey: key,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
Status: p.Status,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: p.FallbackMode,
|
||||
BackupProxyName: backupProxyName,
|
||||
ExpiryWarnDays: p.ExpiryWarnDays,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -207,10 +231,15 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
||||
}
|
||||
|
||||
proxyKeyToID := make(map[string]int64, len(existingProxies))
|
||||
// proxyNameToID 用于 backup_proxy_name 反查:DB 已有 + 本批次新建均会写入
|
||||
proxyNameToID := make(map[string]int64, len(existingProxies))
|
||||
for i := range existingProxies {
|
||||
p := existingProxies[i]
|
||||
key := buildProxyKey(p.Protocol, p.Host, p.Port, p.Username, p.Password)
|
||||
proxyKeyToID[key] = p.ID
|
||||
if p.Name != "" {
|
||||
proxyNameToID[p.Name] = p.ID
|
||||
}
|
||||
}
|
||||
|
||||
for i := range dataPayload.Proxies {
|
||||
@@ -235,21 +264,76 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
||||
result.ProxyReused++
|
||||
if normalizedStatus != "" {
|
||||
if proxy, getErr := h.adminService.GetProxy(ctx, existingID); getErr == nil && proxy != nil && proxy.Status != normalizedStatus {
|
||||
// 同步 status 时传入完整字段,避免零值覆盖已存在代理的有效期/fallback 配置。
|
||||
var existingExpiresAt *time.Time
|
||||
if item.ExpiresAt != nil {
|
||||
t := time.Unix(*item.ExpiresAt, 0).UTC()
|
||||
existingExpiresAt = &t
|
||||
}
|
||||
existingFallbackMode := item.FallbackMode
|
||||
if existingFallbackMode == "" {
|
||||
existingFallbackMode = service.FallbackModeNone
|
||||
}
|
||||
var existingBackupProxyID *int64
|
||||
if item.BackupProxyName != "" {
|
||||
if bid, ok := proxyNameToID[item.BackupProxyName]; ok {
|
||||
existingBackupProxyID = &bid
|
||||
}
|
||||
}
|
||||
_, _ = h.adminService.UpdateProxy(ctx, existingID, &service.UpdateProxyInput{
|
||||
Status: normalizedStatus,
|
||||
Status: normalizedStatus,
|
||||
ExpiresAt: existingExpiresAt,
|
||||
FallbackMode: existingFallbackMode,
|
||||
BackupProxyID: existingBackupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
Name: proxy.Name,
|
||||
Protocol: proxy.Protocol,
|
||||
Host: proxy.Host,
|
||||
Port: proxy.Port,
|
||||
Username: proxy.Username,
|
||||
Password: proxy.Password,
|
||||
})
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析 expires_at(unix 秒 → *time.Time)
|
||||
var expiresAt *time.Time
|
||||
if item.ExpiresAt != nil {
|
||||
t := time.Unix(*item.ExpiresAt, 0).UTC()
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
// 解析 backup_proxy_name → backup_proxy_id
|
||||
fallbackMode := item.FallbackMode
|
||||
var backupProxyID *int64
|
||||
if item.BackupProxyName != "" {
|
||||
if bid, ok := proxyNameToID[item.BackupProxyName]; ok {
|
||||
backupProxyID = &bid
|
||||
} else {
|
||||
// 查不到备用代理:降级 fallback_mode=none,记录 warning
|
||||
fallbackMode = service.FallbackModeNone
|
||||
result.Errors = append(result.Errors, DataImportError{
|
||||
Kind: "proxy",
|
||||
Name: item.Name,
|
||||
ProxyKey: key,
|
||||
Message: fmt.Sprintf("backup_proxy_name %q not found, fallback_mode downgraded to none", item.BackupProxyName),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
created, createErr := h.adminService.CreateProxy(ctx, &service.CreateProxyInput{
|
||||
Name: defaultProxyName(item.Name),
|
||||
Protocol: item.Protocol,
|
||||
Host: item.Host,
|
||||
Port: item.Port,
|
||||
Username: item.Username,
|
||||
Password: item.Password,
|
||||
Name: defaultProxyName(item.Name),
|
||||
Protocol: item.Protocol,
|
||||
Host: item.Host,
|
||||
Port: item.Port,
|
||||
Username: item.Username,
|
||||
Password: item.Password,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: fallbackMode,
|
||||
BackupProxyID: backupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
})
|
||||
if createErr != nil {
|
||||
result.ProxyFailed++
|
||||
@@ -262,11 +346,26 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
|
||||
continue
|
||||
}
|
||||
proxyKeyToID[key] = created.ID
|
||||
// 把新建代理的 name 也加入反查表,供后续批内代理引用
|
||||
if created.Name != "" {
|
||||
proxyNameToID[created.Name] = created.ID
|
||||
}
|
||||
result.ProxyCreated++
|
||||
|
||||
if normalizedStatus != "" && normalizedStatus != created.Status {
|
||||
// 新建后同步 status 时,传入完整字段,避免零值覆盖刚创建的有效期/fallback 配置。
|
||||
_, _ = h.adminService.UpdateProxy(ctx, created.ID, &service.UpdateProxyInput{
|
||||
Status: normalizedStatus,
|
||||
Status: normalizedStatus,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: fallbackMode,
|
||||
BackupProxyID: backupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
Name: created.Name,
|
||||
Protocol: created.Protocol,
|
||||
Host: created.Host,
|
||||
Port: created.Port,
|
||||
Username: created.Username,
|
||||
Password: created.Password,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -646,6 +745,9 @@ func normalizeProxyStatus(status string) string {
|
||||
return service.StatusActive
|
||||
case "inactive", service.StatusDisabled:
|
||||
return "inactive"
|
||||
case "expired":
|
||||
// 导入 expired 代理按 inactive 处理,避免导入即触发到期改投逻辑
|
||||
return "inactive"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -1132,6 +1132,21 @@ func (h *AccountHandler) ClearError(c *gin.Context) {
|
||||
response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account))
|
||||
}
|
||||
|
||||
// RevertProxyFallback handles reverting account proxy to original before fallback.
|
||||
// POST /api/v1/admin/accounts/:id/revert-proxy-fallback
|
||||
func (h *AccountHandler) RevertProxyFallback(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "Invalid account ID")
|
||||
return
|
||||
}
|
||||
if err := h.adminService.RevertAccountProxyFallback(c.Request.Context(), id); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, gin.H{"message": "reverted"})
|
||||
}
|
||||
|
||||
// BatchClearError handles batch clearing account errors
|
||||
// POST /api/v1/admin/accounts/batch-clear-error
|
||||
func (h *AccountHandler) BatchClearError(c *gin.Context) {
|
||||
|
||||
@@ -628,5 +628,9 @@ func (s *stubAdminService) ReplaceUserGroup(ctx context.Context, userID, oldGrou
|
||||
return &service.ReplaceUserGroupResult{MigratedKeys: 0}, nil
|
||||
}
|
||||
|
||||
func (s *stubAdminService) RevertAccountProxyFallback(ctx context.Context, id int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure stub implements interface.
|
||||
var _ service.AdminService = (*stubAdminService)(nil)
|
||||
|
||||
@@ -31,6 +31,8 @@ var validOpsAlertMetricTypes = []string{
|
||||
"account_error_ratio",
|
||||
"account_temp_unscheduled_count",
|
||||
"overload_account_count",
|
||||
"proxy_expired_count",
|
||||
"proxy_expiring_soon_count",
|
||||
}
|
||||
|
||||
var validOpsAlertMetricTypeSet = func() map[string]struct{} {
|
||||
|
||||
@@ -46,19 +46,39 @@ func (h *ProxyHandler) ExportData(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 构建 id→name 映射,用于导出备用代理 name
|
||||
proxyNameByID := make(map[int64]string, len(proxies))
|
||||
for i := range proxies {
|
||||
proxyNameByID[proxies[i].ID] = proxies[i].Name
|
||||
}
|
||||
|
||||
dataProxies := make([]DataProxy, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
p := proxies[i]
|
||||
key := buildProxyKey(p.Protocol, p.Host, p.Port, p.Username, p.Password)
|
||||
|
||||
var expiresAt *int64
|
||||
if p.ExpiresAt != nil {
|
||||
v := p.ExpiresAt.Unix()
|
||||
expiresAt = &v
|
||||
}
|
||||
var backupProxyName string
|
||||
if p.BackupProxyID != nil {
|
||||
backupProxyName = proxyNameByID[*p.BackupProxyID]
|
||||
}
|
||||
dataProxies = append(dataProxies, DataProxy{
|
||||
ProxyKey: key,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
Status: p.Status,
|
||||
ProxyKey: key,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
Status: p.Status,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: p.FallbackMode,
|
||||
BackupProxyName: backupProxyName,
|
||||
ExpiryWarnDays: p.ExpiryWarnDays,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,10 +118,15 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
|
||||
}
|
||||
|
||||
proxyByKey := make(map[string]service.Proxy, len(existingProxies))
|
||||
// proxyNameToID 用于 backup_proxy_name 反查:DB 已有 + 本批次新建均会写入
|
||||
proxyNameToID := make(map[string]int64, len(existingProxies))
|
||||
for i := range existingProxies {
|
||||
p := existingProxies[i]
|
||||
key := buildProxyKey(p.Protocol, p.Host, p.Port, p.Username, p.Password)
|
||||
proxyByKey[key] = p
|
||||
if p.Name != "" {
|
||||
proxyNameToID[p.Name] = p.ID
|
||||
}
|
||||
}
|
||||
|
||||
latencyProbeIDs := make([]int64, 0, len(req.Data.Proxies))
|
||||
@@ -127,7 +152,38 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
|
||||
if existing, ok := proxyByKey[key]; ok {
|
||||
result.ProxyReused++
|
||||
if normalizedStatus != "" && normalizedStatus != existing.Status {
|
||||
if _, err := h.adminService.UpdateProxy(ctx, existing.ID, &service.UpdateProxyInput{Status: normalizedStatus}); err != nil {
|
||||
// 已存在代理同步 status 时,同时保留/覆盖导入 item 的完整字段,
|
||||
// 避免 UpdateProxy 零值覆盖有效期/fallback 配置。
|
||||
var existingExpiresAt *time.Time
|
||||
if item.ExpiresAt != nil {
|
||||
t := time.Unix(*item.ExpiresAt, 0).UTC()
|
||||
existingExpiresAt = &t
|
||||
}
|
||||
existingFallbackMode := item.FallbackMode
|
||||
if existingFallbackMode == "" {
|
||||
existingFallbackMode = service.FallbackModeNone
|
||||
}
|
||||
var existingBackupProxyID *int64
|
||||
if item.BackupProxyName != "" {
|
||||
if bid, ok := proxyNameToID[item.BackupProxyName]; ok {
|
||||
existingBackupProxyID = &bid
|
||||
}
|
||||
}
|
||||
updateInput := &service.UpdateProxyInput{
|
||||
Status: normalizedStatus,
|
||||
ExpiresAt: existingExpiresAt,
|
||||
FallbackMode: existingFallbackMode,
|
||||
BackupProxyID: existingBackupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
// 保留已存在代理的网络配置字段
|
||||
Name: existing.Name,
|
||||
Protocol: existing.Protocol,
|
||||
Host: existing.Host,
|
||||
Port: existing.Port,
|
||||
Username: existing.Username,
|
||||
Password: existing.Password,
|
||||
}
|
||||
if _, err := h.adminService.UpdateProxy(ctx, existing.ID, updateInput); err != nil {
|
||||
result.Errors = append(result.Errors, DataImportError{
|
||||
Kind: "proxy",
|
||||
Name: item.Name,
|
||||
@@ -140,13 +196,42 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析 expires_at(unix 秒 → *time.Time)
|
||||
var expiresAt *time.Time
|
||||
if item.ExpiresAt != nil {
|
||||
t := time.Unix(*item.ExpiresAt, 0).UTC()
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
// 解析 backup_proxy_name → backup_proxy_id
|
||||
fallbackMode := item.FallbackMode
|
||||
var backupProxyID *int64
|
||||
if item.BackupProxyName != "" {
|
||||
if bid, ok := proxyNameToID[item.BackupProxyName]; ok {
|
||||
backupProxyID = &bid
|
||||
} else {
|
||||
// 查不到备用代理:降级 fallback_mode=none,记录 warning
|
||||
fallbackMode = service.FallbackModeNone
|
||||
result.Errors = append(result.Errors, DataImportError{
|
||||
Kind: "proxy",
|
||||
Name: item.Name,
|
||||
ProxyKey: key,
|
||||
Message: fmt.Sprintf("backup_proxy_name %q not found, fallback_mode downgraded to none", item.BackupProxyName),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
created, err := h.adminService.CreateProxy(ctx, &service.CreateProxyInput{
|
||||
Name: defaultProxyName(item.Name),
|
||||
Protocol: item.Protocol,
|
||||
Host: item.Host,
|
||||
Port: item.Port,
|
||||
Username: item.Username,
|
||||
Password: item.Password,
|
||||
Name: defaultProxyName(item.Name),
|
||||
Protocol: item.Protocol,
|
||||
Host: item.Host,
|
||||
Port: item.Port,
|
||||
Username: item.Username,
|
||||
Password: item.Password,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: fallbackMode,
|
||||
BackupProxyID: backupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
})
|
||||
if err != nil {
|
||||
result.ProxyFailed++
|
||||
@@ -160,9 +245,26 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
|
||||
}
|
||||
result.ProxyCreated++
|
||||
proxyByKey[key] = *created
|
||||
// 把新建代理的 name 也加入反查表,供后续批内代理引用
|
||||
if created.Name != "" {
|
||||
proxyNameToID[created.Name] = created.ID
|
||||
}
|
||||
|
||||
if normalizedStatus != "" && normalizedStatus != created.Status {
|
||||
if _, err := h.adminService.UpdateProxy(ctx, created.ID, &service.UpdateProxyInput{Status: normalizedStatus}); err != nil {
|
||||
// 新建后同步 status 时,传入完整字段,避免零值覆盖刚创建的有效期/fallback 配置。
|
||||
if _, err := h.adminService.UpdateProxy(ctx, created.ID, &service.UpdateProxyInput{
|
||||
Status: normalizedStatus,
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: fallbackMode,
|
||||
BackupProxyID: backupProxyID,
|
||||
ExpiryWarnDays: item.ExpiryWarnDays,
|
||||
Name: created.Name,
|
||||
Protocol: created.Protocol,
|
||||
Host: created.Host,
|
||||
Port: created.Port,
|
||||
Username: created.Username,
|
||||
Password: created.Password,
|
||||
}); err != nil {
|
||||
result.Errors = append(result.Errors, DataImportError{
|
||||
Kind: "proxy",
|
||||
Name: item.Name,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
@@ -26,23 +27,31 @@ func NewProxyHandler(adminService service.AdminService) *ProxyHandler {
|
||||
|
||||
// CreateProxyRequest represents create proxy request
|
||||
type CreateProxyRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Protocol string `json:"protocol" binding:"required,oneof=http https socks5 socks5h"`
|
||||
Host string `json:"host" binding:"required"`
|
||||
Port int `json:"port" binding:"required,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Protocol string `json:"protocol" binding:"required,oneof=http https socks5 socks5h"`
|
||||
Host string `json:"host" binding:"required"`
|
||||
Port int `json:"port" binding:"required,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
FallbackMode string `json:"fallback_mode" binding:"omitempty,oneof=none proxy direct"`
|
||||
BackupProxyID *int64 `json:"backup_proxy_id"`
|
||||
ExpiryWarnDays int `json:"expiry_warn_days" binding:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
// UpdateProxyRequest represents update proxy request
|
||||
type UpdateProxyRequest struct {
|
||||
Name string `json:"name"`
|
||||
Protocol string `json:"protocol" binding:"omitempty,oneof=http https socks5 socks5h"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||
Name string `json:"name"`
|
||||
Protocol string `json:"protocol" binding:"omitempty,oneof=http https socks5 socks5h"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port" binding:"omitempty,min=1,max=65535"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
FallbackMode string `json:"fallback_mode" binding:"omitempty,oneof=none proxy direct"`
|
||||
BackupProxyID *int64 `json:"backup_proxy_id"`
|
||||
ExpiryWarnDays int `json:"expiry_warn_days" binding:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
// List handles listing all proxies with pagination
|
||||
@@ -134,13 +143,22 @@ func (h *ProxyHandler) Create(c *gin.Context) {
|
||||
}
|
||||
|
||||
executeAdminIdempotentJSON(c, "admin.proxies.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
|
||||
var expiresAt *time.Time
|
||||
if req.ExpiresAt != nil && *req.ExpiresAt > 0 {
|
||||
t := time.Unix(*req.ExpiresAt, 0).UTC()
|
||||
expiresAt = &t
|
||||
}
|
||||
proxy, err := h.adminService.CreateProxy(ctx, &service.CreateProxyInput{
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Protocol: strings.TrimSpace(req.Protocol),
|
||||
Host: strings.TrimSpace(req.Host),
|
||||
Port: req.Port,
|
||||
Username: strings.TrimSpace(req.Username),
|
||||
Password: strings.TrimSpace(req.Password),
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Protocol: strings.TrimSpace(req.Protocol),
|
||||
Host: strings.TrimSpace(req.Host),
|
||||
Port: req.Port,
|
||||
Username: strings.TrimSpace(req.Username),
|
||||
Password: strings.TrimSpace(req.Password),
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: strings.TrimSpace(req.FallbackMode),
|
||||
BackupProxyID: req.BackupProxyID,
|
||||
ExpiryWarnDays: req.ExpiryWarnDays,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,14 +182,23 @@ func (h *ProxyHandler) Update(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var expiresAt *time.Time
|
||||
if req.ExpiresAt != nil && *req.ExpiresAt > 0 {
|
||||
t := time.Unix(*req.ExpiresAt, 0).UTC()
|
||||
expiresAt = &t
|
||||
}
|
||||
proxy, err := h.adminService.UpdateProxy(c.Request.Context(), proxyID, &service.UpdateProxyInput{
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Protocol: strings.TrimSpace(req.Protocol),
|
||||
Host: strings.TrimSpace(req.Host),
|
||||
Port: req.Port,
|
||||
Username: strings.TrimSpace(req.Username),
|
||||
Password: strings.TrimSpace(req.Password),
|
||||
Status: strings.TrimSpace(req.Status),
|
||||
Name: strings.TrimSpace(req.Name),
|
||||
Protocol: strings.TrimSpace(req.Protocol),
|
||||
Host: strings.TrimSpace(req.Host),
|
||||
Port: req.Port,
|
||||
Username: strings.TrimSpace(req.Username),
|
||||
Password: strings.TrimSpace(req.Password),
|
||||
Status: strings.TrimSpace(req.Status),
|
||||
ExpiresAt: expiresAt,
|
||||
FallbackMode: strings.TrimSpace(req.FallbackMode),
|
||||
BackupProxyID: req.BackupProxyID,
|
||||
ExpiryWarnDays: req.ExpiryWarnDays,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
|
||||
@@ -211,6 +211,8 @@ func AccountFromServiceShallow(a *service.Account) *Account {
|
||||
CredentialsStatus: credsStatus,
|
||||
Extra: a.Extra,
|
||||
ProxyID: a.ProxyID,
|
||||
ProxyFallbackOriginID: a.ProxyFallbackOriginID,
|
||||
ProxyFallbackOriginName: a.ProxyFallbackOriginName,
|
||||
Concurrency: a.Concurrency,
|
||||
LoadFactor: a.LoadFactor,
|
||||
Priority: a.Priority,
|
||||
@@ -412,15 +414,19 @@ func ProxyFromService(p *service.Proxy) *Proxy {
|
||||
return nil
|
||||
}
|
||||
return &Proxy{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Status: p.Status,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Protocol: p.Protocol,
|
||||
Host: p.Host,
|
||||
Port: p.Port,
|
||||
Username: p.Username,
|
||||
Status: p.Status,
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
ExpiresAt: p.ExpiresAt,
|
||||
FallbackMode: p.FallbackMode,
|
||||
BackupProxyID: p.BackupProxyID,
|
||||
ExpiryWarnDays: p.ExpiryWarnDays,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,21 +160,23 @@ type Account struct {
|
||||
Type string `json:"type"`
|
||||
// Credentials 经 RedactCredentials 处理后只含非敏感子键;敏感 token / api_key / 私钥
|
||||
// 的存在性通过 CredentialsStatus(has_<key>)暴露,原始值不返回前端。
|
||||
Credentials map[string]any `json:"credentials"`
|
||||
CredentialsStatus map[string]bool `json:"credentials_status,omitempty"`
|
||||
Extra map[string]any `json:"extra"`
|
||||
ProxyID *int64 `json:"proxy_id"`
|
||||
Concurrency int `json:"concurrency"`
|
||||
LoadFactor *int `json:"load_factor,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
RateMultiplier float64 `json:"rate_multiplier"`
|
||||
Status string `json:"status"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
AutoPauseOnExpired bool `json:"auto_pause_on_expired"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Credentials map[string]any `json:"credentials"`
|
||||
CredentialsStatus map[string]bool `json:"credentials_status,omitempty"`
|
||||
Extra map[string]any `json:"extra"`
|
||||
ProxyID *int64 `json:"proxy_id"`
|
||||
ProxyFallbackOriginID *int64 `json:"proxy_fallback_origin_id"`
|
||||
ProxyFallbackOriginName *string `json:"proxy_fallback_origin_name,omitempty"`
|
||||
Concurrency int `json:"concurrency"`
|
||||
LoadFactor *int `json:"load_factor,omitempty"`
|
||||
Priority int `json:"priority"`
|
||||
RateMultiplier float64 `json:"rate_multiplier"`
|
||||
Status string `json:"status"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
LastUsedAt *time.Time `json:"last_used_at"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
AutoPauseOnExpired bool `json:"auto_pause_on_expired"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
Schedulable bool `json:"schedulable"`
|
||||
|
||||
@@ -279,6 +281,11 @@ type Proxy struct {
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
FallbackMode string `json:"fallback_mode"`
|
||||
BackupProxyID *int64 `json:"backup_proxy_id"`
|
||||
ExpiryWarnDays int `json:"expiry_warn_days"`
|
||||
}
|
||||
|
||||
type ProxyWithAccountCount struct {
|
||||
|
||||
@@ -1581,6 +1581,9 @@ func (r *accountRepository) accountsToService(ctx context.Context, accounts []*d
|
||||
if acc.ProxyID != nil {
|
||||
proxyIDs = append(proxyIDs, *acc.ProxyID)
|
||||
}
|
||||
if acc.ProxyFallbackOriginID != nil {
|
||||
proxyIDs = append(proxyIDs, *acc.ProxyFallbackOriginID)
|
||||
}
|
||||
}
|
||||
|
||||
proxyMap, err := r.loadProxies(ctx, proxyIDs)
|
||||
@@ -1603,6 +1606,13 @@ func (r *accountRepository) accountsToService(ctx context.Context, accounts []*d
|
||||
out.Proxy = proxy
|
||||
}
|
||||
}
|
||||
out.ProxyFallbackOriginID = acc.ProxyFallbackOriginID
|
||||
if acc.ProxyFallbackOriginID != nil {
|
||||
if op, ok := proxyMap[*acc.ProxyFallbackOriginID]; ok && op != nil {
|
||||
n := op.Name
|
||||
out.ProxyFallbackOriginName = &n
|
||||
}
|
||||
}
|
||||
if groups, ok := groupsByAccount[acc.ID]; ok {
|
||||
out.Groups = groups
|
||||
}
|
||||
@@ -1754,6 +1764,7 @@ func accountEntityToService(m *dbent.Account) *service.Account {
|
||||
Credentials: copyJSONMap(m.Credentials),
|
||||
Extra: copyJSONMap(m.Extra),
|
||||
ProxyID: m.ProxyID,
|
||||
ProxyFallbackOriginID: m.ProxyFallbackOriginID,
|
||||
Concurrency: m.Concurrency,
|
||||
Priority: m.Priority,
|
||||
RateMultiplier: &rateMultiplier,
|
||||
@@ -2046,3 +2057,23 @@ func (r *accountRepository) ResetQuotaUsed(ctx context.Context, id int64) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevertProxyFallback 将账号的 proxy_id 切回 proxy_fallback_origin_id,并清空 origin 字段。
|
||||
// 仅当 proxy_fallback_origin_id IS NOT NULL 时执行更新;
|
||||
// 若影响行数为 0,则返回 ErrAccountNotInFallback(账号存在但不在 fallback 状态)。
|
||||
func (r *accountRepository) RevertProxyFallback(ctx context.Context, accountID int64) error {
|
||||
res, err := r.sql.ExecContext(ctx, `
|
||||
UPDATE accounts SET proxy_id=proxy_fallback_origin_id, proxy_fallback_origin_id=NULL, updated_at=NOW()
|
||||
WHERE id=$1 AND proxy_fallback_origin_id IS NOT NULL AND deleted_at IS NULL`, accountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
if n == 0 {
|
||||
return service.ErrAccountNotInFallback
|
||||
}
|
||||
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventAccountChanged, &accountID, nil, nil); err != nil {
|
||||
logger.LegacyPrintf("repository.account", "[SchedulerOutbox] revert fallback enqueue failed: account=%d err=%v", accountID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
//go:build integration
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ProxyExpirySuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
tx *dbent.Tx
|
||||
repo *proxyRepository
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) SetupTest() {
|
||||
s.ctx = context.Background()
|
||||
s.tx = testEntTx(s.T())
|
||||
s.repo = newProxyRepositoryWithSQL(s.tx.Client(), s.tx)
|
||||
}
|
||||
func TestProxyExpirySuite(t *testing.T) { suite.Run(t, new(ProxyExpirySuite)) }
|
||||
|
||||
func (s *ProxyExpirySuite) mkProxy(name, mode string, expiresAt *time.Time, backupID *int64) int64 {
|
||||
p := &service.Proxy{Name: name, Protocol: "http", Host: "127.0.0.1", Port: 8080,
|
||||
Status: service.StatusActive, FallbackMode: mode, ExpiryWarnDays: 7,
|
||||
ExpiresAt: expiresAt, BackupProxyID: backupID}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, p))
|
||||
return p.ID
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) mkAccountWithProxy(proxyID int64) int64 {
|
||||
var id int64
|
||||
err := scanSingleRow(s.ctx, s.tx, `
|
||||
INSERT INTO accounts (name, platform, type, credentials, extra, status, proxy_id, created_at, updated_at)
|
||||
VALUES ($1,'claude','api','{}','{}','active',$2,NOW(),NOW()) RETURNING id`,
|
||||
[]any{"acc-" + time.Now().Format("150405.000000"), proxyID}, &id)
|
||||
s.Require().NoError(err)
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) accountProxyID(id int64) *int64 {
|
||||
var pid *int64
|
||||
err := scanSingleRow(s.ctx, s.tx, `SELECT proxy_id FROM accounts WHERE id=$1`, []any{id}, &pid)
|
||||
s.Require().NoError(err)
|
||||
return pid
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) TestSweep_DirectMode() {
|
||||
past := time.Now().Add(-time.Hour)
|
||||
pid := s.mkProxy("p-direct", service.FallbackModeDirect, &past, nil)
|
||||
aid := s.mkAccountWithProxy(pid)
|
||||
|
||||
changed, err := s.repo.SweepExpiredProxies(s.ctx, time.Now())
|
||||
s.Require().NoError(err)
|
||||
s.Require().GreaterOrEqual(changed, int64(1))
|
||||
|
||||
got, _ := s.repo.GetByID(s.ctx, pid)
|
||||
s.Require().Equal(service.StatusExpired, got.Status)
|
||||
s.Require().Nil(s.accountProxyID(aid))
|
||||
var origin *int64
|
||||
err = scanSingleRow(s.ctx, s.tx, `SELECT proxy_fallback_origin_id FROM accounts WHERE id=$1`, []any{aid}, &origin)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(origin)
|
||||
s.Require().Equal(pid, *origin)
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) TestSweep_ProxyMode_Healthy() {
|
||||
future := time.Now().Add(24 * time.Hour)
|
||||
past := time.Now().Add(-time.Hour)
|
||||
backup := s.mkProxy("p-backup", service.FallbackModeNone, &future, nil)
|
||||
pid := s.mkProxy("p-main", service.FallbackModeProxy, &past, &backup)
|
||||
aid := s.mkAccountWithProxy(pid)
|
||||
|
||||
_, err := s.repo.SweepExpiredProxies(s.ctx, time.Now())
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(backup, *s.accountProxyID(aid))
|
||||
var origin *int64
|
||||
err = scanSingleRow(s.ctx, s.tx, `SELECT proxy_fallback_origin_id FROM accounts WHERE id=$1`, []any{aid}, &origin)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(origin)
|
||||
s.Require().Equal(pid, *origin)
|
||||
}
|
||||
|
||||
func (s *ProxyExpirySuite) TestSweep_NoneMode_KeepsAccount() {
|
||||
past := time.Now().Add(-time.Hour)
|
||||
pid := s.mkProxy("p-none", service.FallbackModeNone, &past, nil)
|
||||
aid := s.mkAccountWithProxy(pid)
|
||||
|
||||
_, err := s.repo.SweepExpiredProxies(s.ctx, time.Now())
|
||||
s.Require().NoError(err)
|
||||
got, _ := s.repo.GetByID(s.ctx, pid)
|
||||
s.Require().Equal(service.StatusExpired, got.Status)
|
||||
s.Require().Equal(pid, *s.accountProxyID(aid))
|
||||
var origin *int64
|
||||
err = scanSingleRow(s.ctx, s.tx, `SELECT proxy_fallback_origin_id FROM accounts WHERE id=$1`, []any{aid}, &origin)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Nil(origin)
|
||||
}
|
||||
@@ -5,30 +5,29 @@ import (
|
||||
"database/sql"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
entsql "entgo.io/ent/dialect/sql"
|
||||
)
|
||||
|
||||
type sqlQuerier interface {
|
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||
}
|
||||
|
||||
// sqlQuerier 已替换为 sqlExecutor(定义在 group_repo.go),
|
||||
// proxyRepository 使用同一接口以支持 ExecContext。
|
||||
type proxyRepository struct {
|
||||
client *dbent.Client
|
||||
sql sqlQuerier
|
||||
sql sqlExecutor
|
||||
}
|
||||
|
||||
func NewProxyRepository(client *dbent.Client, sqlDB *sql.DB) service.ProxyRepository {
|
||||
return newProxyRepositoryWithSQL(client, sqlDB)
|
||||
}
|
||||
|
||||
func newProxyRepositoryWithSQL(client *dbent.Client, sqlq sqlQuerier) *proxyRepository {
|
||||
func newProxyRepositoryWithSQL(client *dbent.Client, sqlq sqlExecutor) *proxyRepository {
|
||||
return &proxyRepository{client: client, sql: sqlq}
|
||||
}
|
||||
|
||||
@@ -38,13 +37,21 @@ func (r *proxyRepository) Create(ctx context.Context, proxyIn *service.Proxy) er
|
||||
SetProtocol(proxyIn.Protocol).
|
||||
SetHost(proxyIn.Host).
|
||||
SetPort(proxyIn.Port).
|
||||
SetStatus(proxyIn.Status)
|
||||
SetStatus(proxyIn.Status).
|
||||
SetFallbackMode(proxyIn.FallbackMode).
|
||||
SetExpiryWarnDays(proxyIn.ExpiryWarnDays)
|
||||
if proxyIn.Username != "" {
|
||||
builder.SetUsername(proxyIn.Username)
|
||||
}
|
||||
if proxyIn.Password != "" {
|
||||
builder.SetPassword(proxyIn.Password)
|
||||
}
|
||||
if proxyIn.ExpiresAt != nil {
|
||||
builder.SetExpiresAt(*proxyIn.ExpiresAt)
|
||||
}
|
||||
if proxyIn.BackupProxyID != nil {
|
||||
builder.SetBackupProxyID(*proxyIn.BackupProxyID)
|
||||
}
|
||||
|
||||
created, err := builder.Save(ctx)
|
||||
if err == nil {
|
||||
@@ -89,7 +96,9 @@ func (r *proxyRepository) Update(ctx context.Context, proxyIn *service.Proxy) er
|
||||
SetProtocol(proxyIn.Protocol).
|
||||
SetHost(proxyIn.Host).
|
||||
SetPort(proxyIn.Port).
|
||||
SetStatus(proxyIn.Status)
|
||||
SetStatus(proxyIn.Status).
|
||||
SetFallbackMode(proxyIn.FallbackMode).
|
||||
SetExpiryWarnDays(proxyIn.ExpiryWarnDays)
|
||||
if proxyIn.Username != "" {
|
||||
builder.SetUsername(proxyIn.Username)
|
||||
} else {
|
||||
@@ -100,6 +109,16 @@ func (r *proxyRepository) Update(ctx context.Context, proxyIn *service.Proxy) er
|
||||
} else {
|
||||
builder.ClearPassword()
|
||||
}
|
||||
if proxyIn.ExpiresAt != nil {
|
||||
builder.SetExpiresAt(*proxyIn.ExpiresAt)
|
||||
} else {
|
||||
builder.ClearExpiresAt()
|
||||
}
|
||||
if proxyIn.BackupProxyID != nil {
|
||||
builder.SetBackupProxyID(*proxyIn.BackupProxyID)
|
||||
} else {
|
||||
builder.ClearBackupProxyID()
|
||||
}
|
||||
|
||||
updated, err := builder.Save(ctx)
|
||||
if err == nil {
|
||||
@@ -258,6 +277,12 @@ func proxyListOrder(params pagination.PaginationParams) []func(*entsql.Selector)
|
||||
field = proxy.FieldStatus
|
||||
case "created_at":
|
||||
field = proxy.FieldCreatedAt
|
||||
case "expiry":
|
||||
// expires_at 可空(NULL=永不过期)。不写显式 NULLS:
|
||||
// dbent.Asc/Desc 不带 NULLS 子句,继承 PG 默认
|
||||
// (ASC→NULLS LAST、DESC→NULLS FIRST),即 NULL 视为最晚——
|
||||
// 升序垫底、降序置顶。
|
||||
field = proxy.FieldExpiresAt
|
||||
default:
|
||||
field = proxy.FieldID
|
||||
}
|
||||
@@ -417,14 +442,18 @@ func proxyEntityToService(m *dbent.Proxy) *service.Proxy {
|
||||
return nil
|
||||
}
|
||||
out := &service.Proxy{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Protocol: m.Protocol,
|
||||
Host: m.Host,
|
||||
Port: m.Port,
|
||||
Status: m.Status,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Protocol: m.Protocol,
|
||||
Host: m.Host,
|
||||
Port: m.Port,
|
||||
Status: m.Status,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
ExpiresAt: m.ExpiresAt,
|
||||
FallbackMode: m.FallbackMode,
|
||||
BackupProxyID: m.BackupProxyID,
|
||||
ExpiryWarnDays: m.ExpiryWarnDays,
|
||||
}
|
||||
if m.Username != nil {
|
||||
out.Username = *m.Username
|
||||
@@ -443,3 +472,139 @@ func applyProxyEntityToService(dst *service.Proxy, src *dbent.Proxy) {
|
||||
dst.CreatedAt = src.CreatedAt
|
||||
dst.UpdatedAt = src.UpdatedAt
|
||||
}
|
||||
|
||||
// ListAllForFallback 返回所有代理(含过期/非活跃),供改投逻辑使用。
|
||||
func (r *proxyRepository) ListAllForFallback(ctx context.Context) ([]service.Proxy, error) {
|
||||
proxies, err := r.client.Proxy.Query().All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]service.Proxy, 0, len(proxies))
|
||||
for i := range proxies {
|
||||
out = append(out, *proxyEntityToService(proxies[i]))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SweepExpiredProxies 扫描到期 active 代理,标记 expired 并按 fallback 策略改写绑定账号的 proxy_id,
|
||||
// 最终触发 scheduler outbox 使 Redis 快照缓存失效。返回受影响的账号行数。
|
||||
// 原子性边界:每个过期代理的「标记 expired + 改投账号」在各自子事务内原子执行(见 sweepOneExpiredProxy);
|
||||
// 全部代理处理完后若有账号被改投,再统一 enqueue 一次 full_rebuild 事件——该 enqueue 在子事务之外
|
||||
// (走 r.sql、失败仅记日志、由调度器周期性 full rebuild 兜底),故「改投 → 失效」整体并非原子。
|
||||
func (r *proxyRepository) SweepExpiredProxies(ctx context.Context, now time.Time) (int64, error) {
|
||||
// 快照读(事务前):允许脏读不影响正确性,事务内已加锁写。
|
||||
all, err := r.ListAllForFallback(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
byID := make(map[int64]service.Proxy, len(all))
|
||||
for _, p := range all {
|
||||
byID[p.ID] = p
|
||||
}
|
||||
|
||||
var totalChanged int64
|
||||
accountsTouched := false
|
||||
|
||||
for _, p := range all {
|
||||
if p.Status != service.StatusActive || !p.IsExpired(now) {
|
||||
continue
|
||||
}
|
||||
|
||||
target, change := service.ResolveProxyFallbackTarget(p, byID, now)
|
||||
if !change && p.FallbackMode == service.FallbackModeProxy {
|
||||
// 配置了 proxy 回退但链路无解(成环或全部已过期),记录告警日志
|
||||
logger.LegacyPrintf("repository.proxy", "[ProxyExpiry] proxy %d expired but fallback chain unresolved (cycle/all-expired); accounts kept", p.ID)
|
||||
}
|
||||
|
||||
changed, sweepErr := r.sweepOneExpiredProxy(ctx, p.ID, target, change)
|
||||
if sweepErr != nil {
|
||||
return totalChanged, sweepErr
|
||||
}
|
||||
if changed > 0 {
|
||||
totalChanged += changed
|
||||
accountsTouched = true
|
||||
}
|
||||
}
|
||||
|
||||
if accountsTouched {
|
||||
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventFullRebuild, nil, nil, nil); err != nil {
|
||||
logger.LegacyPrintf("repository.proxy", "[SchedulerOutbox] enqueue proxy expiry rebuild failed: err=%v", err)
|
||||
}
|
||||
}
|
||||
return totalChanged, nil
|
||||
}
|
||||
|
||||
// sweepOneExpiredProxy 在单事务内原子执行:标记代理 expired + 改投绑定账号。
|
||||
// 若 r.client 已绑定事务(测试注入场景),直接在 r.sql 上执行,由外层事务保证原子性。
|
||||
func (r *proxyRepository) sweepOneExpiredProxy(ctx context.Context, proxyID int64, target *int64, change bool) (int64, error) {
|
||||
// 尝试开启子事务;若 r.client 已是事务 client,则返回 ErrTxStarted,退回使用 r.sql。
|
||||
tx, txErr := r.client.Tx(ctx)
|
||||
if txErr != nil {
|
||||
if txErr != dbent.ErrTxStarted {
|
||||
return 0, txErr
|
||||
}
|
||||
// 已在外层事务中(集成测试场景),直接用 r.sql 执行
|
||||
return r.sweepOneExpiredProxyOnExec(ctx, r.sql, proxyID, target, change)
|
||||
}
|
||||
|
||||
// 使用新事务执行
|
||||
var n int64
|
||||
var err error
|
||||
n, err = r.sweepOneExpiredProxyOnExec(ctx, tx, proxyID, target, change)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
if commitErr := tx.Commit(); commitErr != nil {
|
||||
return 0, commitErr
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// sweepOneExpiredProxyOnExec 在给定的 sqlExecutor 上执行:标记 expired + 改投账号。
|
||||
func (r *proxyRepository) sweepOneExpiredProxyOnExec(ctx context.Context, exec sqlExecutor, proxyID int64, target *int64, change bool) (int64, error) {
|
||||
if _, err := exec.ExecContext(ctx,
|
||||
`UPDATE proxies SET status=$1, updated_at=NOW() WHERE id=$2 AND deleted_at IS NULL`,
|
||||
service.StatusExpired, proxyID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !change {
|
||||
return 0, nil
|
||||
}
|
||||
var (
|
||||
res sql.Result
|
||||
err error
|
||||
)
|
||||
if target == nil {
|
||||
res, err = exec.ExecContext(ctx, `
|
||||
UPDATE accounts SET proxy_id=NULL, proxy_fallback_origin_id=$1, updated_at=NOW()
|
||||
WHERE proxy_id=$1 AND proxy_fallback_origin_id IS NULL AND deleted_at IS NULL`, proxyID)
|
||||
} else {
|
||||
res, err = exec.ExecContext(ctx, `
|
||||
UPDATE accounts SET proxy_id=$2, proxy_fallback_origin_id=$1, updated_at=NOW()
|
||||
WHERE proxy_id=$1 AND proxy_fallback_origin_id IS NULL AND deleted_at IS NULL`, proxyID, *target)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// CountExpired 返回已过期(status=expired)的代理数量。
|
||||
func (r *proxyRepository) CountExpired(ctx context.Context) (int64, error) {
|
||||
var c int64
|
||||
err := scanSingleRow(ctx, r.sql, `SELECT COUNT(*) FROM proxies WHERE status=$1 AND deleted_at IS NULL`, []any{service.StatusExpired}, &c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// CountExpiringSoon 返回即将到期(在 expiry_warn_days 天内)的活跃代理数量。
|
||||
func (r *proxyRepository) CountExpiringSoon(ctx context.Context, now time.Time) (int64, error) {
|
||||
var c int64
|
||||
err := scanSingleRow(ctx, r.sql, `
|
||||
SELECT COUNT(*) FROM proxies
|
||||
WHERE deleted_at IS NULL AND status=$1 AND expires_at IS NOT NULL
|
||||
AND expires_at > $2 AND expires_at <= $2 + (expiry_warn_days || ' days')::interval`,
|
||||
[]any{service.StatusActive, now}, &c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
)
|
||||
@@ -26,3 +28,41 @@ func (s *ProxyRepoSuite) TestListWithFiltersAndAccountCount_SortByAccountCountDe
|
||||
s.Require().Equal(int64(2), proxies[0].AccountCount)
|
||||
s.Require().Equal(p2.ID, proxies[1].ID)
|
||||
}
|
||||
|
||||
func (s *ProxyRepoSuite) TestListWithFiltersAndAccountCount_SortByExpiry() {
|
||||
now := time.Now()
|
||||
past := now.Add(-24 * time.Hour)
|
||||
soon := now.Add(72 * time.Hour)
|
||||
later := now.Add(100 * 24 * time.Hour)
|
||||
|
||||
// 创建顺序(=ID 升序)刻意打乱,使其不同于任何有效期顺序:
|
||||
// 一旦排序退回按 id(没加 case "expiry"),此测试必然失败。
|
||||
pLater := s.mustCreateProxy(&service.Proxy{Name: "p-later", Protocol: "http", Host: "127.0.0.1", Port: 8080, Status: service.StatusActive, ExpiresAt: &later})
|
||||
pNever := s.mustCreateProxy(&service.Proxy{Name: "p-never", Protocol: "http", Host: "127.0.0.1", Port: 8081, Status: service.StatusActive, ExpiresAt: nil})
|
||||
pExpired := s.mustCreateProxy(&service.Proxy{Name: "p-expired", Protocol: "http", Host: "127.0.0.1", Port: 8082, Status: service.StatusActive, ExpiresAt: &past})
|
||||
pSoon := s.mustCreateProxy(&service.Proxy{Name: "p-soon", Protocol: "http", Host: "127.0.0.1", Port: 8083, Status: service.StatusActive, ExpiresAt: &soon})
|
||||
|
||||
// 升序:最快到期在前,NULL(永不过期)垫底
|
||||
asc, _, err := s.repo.ListWithFiltersAndAccountCount(s.ctx, pagination.PaginationParams{
|
||||
Page: 1, PageSize: 10, SortBy: "expiry", SortOrder: "asc",
|
||||
}, "", "", "")
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(asc, 4)
|
||||
s.Require().Equal(
|
||||
[]int64{pExpired.ID, pSoon.ID, pLater.ID, pNever.ID},
|
||||
[]int64{asc[0].ID, asc[1].ID, asc[2].ID, asc[3].ID},
|
||||
"asc: 过期→快到期→远期→永不过期(垫底)",
|
||||
)
|
||||
|
||||
// 降序:NULL(永不过期)置顶
|
||||
desc, _, err := s.repo.ListWithFiltersAndAccountCount(s.ctx, pagination.PaginationParams{
|
||||
Page: 1, PageSize: 10, SortBy: "expiry", SortOrder: "desc",
|
||||
}, "", "", "")
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(desc, 4)
|
||||
s.Require().Equal(
|
||||
[]int64{pNever.ID, pLater.ID, pSoon.ID, pExpired.ID},
|
||||
[]int64{desc[0].ID, desc[1].ID, desc[2].ID, desc[3].ID},
|
||||
"desc: 永不过期(置顶)→远期→快到期→过期",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1796,6 +1796,10 @@ func (s *stubAccountRepo) ListCRSAccountIDs(ctx context.Context) (map[string]int
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (s *stubAccountRepo) RevertProxyFallback(ctx context.Context, accountID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubProxyRepo struct{}
|
||||
|
||||
func (stubProxyRepo) Create(ctx context.Context, proxy *service.Proxy) error {
|
||||
@@ -1850,6 +1854,22 @@ func (stubProxyRepo) ListAccountSummariesByProxyID(ctx context.Context, proxyID
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (stubProxyRepo) SweepExpiredProxies(ctx context.Context, now time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (stubProxyRepo) ListAllForFallback(ctx context.Context) ([]service.Proxy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (stubProxyRepo) CountExpired(ctx context.Context) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (stubProxyRepo) CountExpiringSoon(ctx context.Context, now time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type stubRedeemCodeRepo struct {
|
||||
byUser map[int64][]service.RedeemCode
|
||||
}
|
||||
|
||||
@@ -294,6 +294,7 @@ func registerAccountRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
||||
accounts.POST("/:id/refresh-tier", h.Admin.Account.RefreshTier)
|
||||
accounts.GET("/:id/stats", h.Admin.Account.GetStats)
|
||||
accounts.POST("/:id/clear-error", h.Admin.Account.ClearError)
|
||||
accounts.POST("/:id/revert-proxy-fallback", h.Admin.Account.RevertProxyFallback)
|
||||
accounts.GET("/:id/usage", h.Admin.Account.GetUsage)
|
||||
accounts.GET("/:id/today-stats", h.Admin.Account.GetTodayStats)
|
||||
accounts.POST("/today-stats/batch", h.Admin.Account.GetBatchTodayStats)
|
||||
|
||||
@@ -17,16 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type Account struct {
|
||||
ID int64
|
||||
Name string
|
||||
Notes *string
|
||||
Platform string
|
||||
Type string
|
||||
Credentials map[string]any
|
||||
Extra map[string]any
|
||||
ProxyID *int64
|
||||
Concurrency int
|
||||
Priority int
|
||||
ID int64
|
||||
Name string
|
||||
Notes *string
|
||||
Platform string
|
||||
Type string
|
||||
Credentials map[string]any
|
||||
Extra map[string]any
|
||||
ProxyID *int64
|
||||
ProxyFallbackOriginID *int64
|
||||
ProxyFallbackOriginName *string // 仅展示用
|
||||
Concurrency int
|
||||
Priority int
|
||||
// RateMultiplier 账号计费倍率(>=0,允许 0 表示该账号计费为 0)。
|
||||
// 使用指针用于兼容旧版本调度缓存(Redis)中缺字段的情况:nil 表示按 1.0 处理。
|
||||
RateMultiplier *float64
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAccountNotFound = infraerrors.NotFound("ACCOUNT_NOT_FOUND", "account not found")
|
||||
ErrAccountNilInput = infraerrors.BadRequest("ACCOUNT_NIL_INPUT", "account input cannot be nil")
|
||||
ErrAccountNotFound = infraerrors.NotFound("ACCOUNT_NOT_FOUND", "account not found")
|
||||
ErrAccountNilInput = infraerrors.BadRequest("ACCOUNT_NIL_INPUT", "account input cannot be nil")
|
||||
ErrAccountNotInFallback = infraerrors.BadRequest("ACCOUNT_NOT_IN_FALLBACK", "account is not in proxy fallback state")
|
||||
)
|
||||
|
||||
const AccountListGroupUngrouped int64 = -1
|
||||
@@ -74,6 +75,9 @@ type AccountRepository interface {
|
||||
IncrementQuotaUsed(ctx context.Context, id int64, amount float64) error
|
||||
// ResetQuotaUsed 重置 API Key 账号所有维度的配额用量为 0
|
||||
ResetQuotaUsed(ctx context.Context, id int64) error
|
||||
// RevertProxyFallback 将账号的 proxy_id 切回 proxy_fallback_origin_id,并清空 origin 字段。
|
||||
// 仅当 proxy_fallback_origin_id IS NOT NULL 时更新,否则视为账号不存在(返回 ErrAccountNotFound)。
|
||||
RevertProxyFallback(ctx context.Context, accountID int64) error
|
||||
}
|
||||
|
||||
// AccountBulkUpdate describes the fields that can be updated in a bulk operation.
|
||||
|
||||
@@ -207,6 +207,10 @@ func (s *accountRepoStub) ResetQuotaUsed(ctx context.Context, id int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *accountRepoStub) RevertProxyFallback(ctx context.Context, accountID int64) error {
|
||||
panic("unexpected RevertProxyFallback call")
|
||||
}
|
||||
|
||||
// TestAccountService_Delete_NotFound 测试删除不存在的账号时返回正确的错误。
|
||||
// 预期行为:
|
||||
// - ExistsByID 返回 false(账号不存在)
|
||||
|
||||
@@ -96,6 +96,9 @@ type AdminService interface {
|
||||
SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*Account, error)
|
||||
BulkUpdateAccounts(ctx context.Context, input *BulkUpdateAccountsInput) (*BulkUpdateAccountsResult, error)
|
||||
CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error
|
||||
// RevertAccountProxyFallback 将账号的 proxy_id 切回 proxy_fallback_origin_id,并清空 origin 字段。
|
||||
// 若账号不存在返回 ErrAccountNotFound;若账号存在但不在 fallback 状态,返回 ErrAccountNotInFallback。
|
||||
RevertAccountProxyFallback(ctx context.Context, id int64) error
|
||||
|
||||
// Proxy management
|
||||
ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error)
|
||||
@@ -384,22 +387,30 @@ type BulkUpdateAccountsResult struct {
|
||||
}
|
||||
|
||||
type CreateProxyInput struct {
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
ExpiresAt *time.Time
|
||||
FallbackMode string
|
||||
BackupProxyID *int64
|
||||
ExpiryWarnDays int
|
||||
}
|
||||
|
||||
type UpdateProxyInput struct {
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Status string
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Status string
|
||||
ExpiresAt *time.Time
|
||||
FallbackMode string
|
||||
BackupProxyID *int64
|
||||
ExpiryWarnDays int
|
||||
}
|
||||
|
||||
type GenerateRedeemCodesInput struct {
|
||||
@@ -2998,6 +3009,10 @@ func (s *adminServiceImpl) SetAccountSchedulable(ctx context.Context, id int64,
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *adminServiceImpl) RevertAccountProxyFallback(ctx context.Context, id int64) error {
|
||||
return s.accountRepo.RevertProxyFallback(ctx, id)
|
||||
}
|
||||
|
||||
// Proxy management implementations
|
||||
func (s *adminServiceImpl) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]Proxy, int64, error) {
|
||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize, SortBy: sortBy, SortOrder: sortOrder}
|
||||
@@ -3040,14 +3055,31 @@ func (s *adminServiceImpl) GetProxiesByIDs(ctx context.Context, ids []int64) ([]
|
||||
}
|
||||
|
||||
func (s *adminServiceImpl) CreateProxy(ctx context.Context, input *CreateProxyInput) (*Proxy, error) {
|
||||
// 规范化 fallback_mode
|
||||
mode := input.FallbackMode
|
||||
if mode == "" {
|
||||
mode = FallbackModeNone
|
||||
}
|
||||
// 校验:mode=proxy 必须有 backup
|
||||
if mode == FallbackModeProxy && input.BackupProxyID == nil {
|
||||
return nil, infraerrors.BadRequest("PROXY_BACKUP_REQUIRED", "backup proxy required when fallback_mode=proxy")
|
||||
}
|
||||
if input.ExpiryWarnDays < 0 {
|
||||
return nil, infraerrors.BadRequest("PROXY_WARN_DAYS_INVALID", "expiry_warn_days must be >= 0")
|
||||
}
|
||||
|
||||
proxy := &Proxy{
|
||||
Name: input.Name,
|
||||
Protocol: input.Protocol,
|
||||
Host: input.Host,
|
||||
Port: input.Port,
|
||||
Username: input.Username,
|
||||
Password: input.Password,
|
||||
Status: StatusActive,
|
||||
Name: input.Name,
|
||||
Protocol: input.Protocol,
|
||||
Host: input.Host,
|
||||
Port: input.Port,
|
||||
Username: input.Username,
|
||||
Password: input.Password,
|
||||
Status: StatusActive,
|
||||
ExpiresAt: input.ExpiresAt,
|
||||
FallbackMode: mode,
|
||||
BackupProxyID: input.BackupProxyID,
|
||||
ExpiryWarnDays: input.ExpiryWarnDays,
|
||||
}
|
||||
if err := s.proxyRepo.Create(ctx, proxy); err != nil {
|
||||
return nil, err
|
||||
@@ -3058,6 +3090,23 @@ func (s *adminServiceImpl) CreateProxy(ctx context.Context, input *CreateProxyIn
|
||||
}
|
||||
|
||||
func (s *adminServiceImpl) UpdateProxy(ctx context.Context, id int64, input *UpdateProxyInput) (*Proxy, error) {
|
||||
// 校验:backup_proxy_id 不能是自身
|
||||
if input.BackupProxyID != nil && *input.BackupProxyID == id {
|
||||
return nil, infraerrors.BadRequest("PROXY_BACKUP_SELF", "backup proxy cannot be itself")
|
||||
}
|
||||
// 规范化 fallback_mode
|
||||
mode := input.FallbackMode
|
||||
if mode == "" {
|
||||
mode = FallbackModeNone
|
||||
}
|
||||
// 校验:mode=proxy 必须有 backup
|
||||
if mode == FallbackModeProxy && input.BackupProxyID == nil {
|
||||
return nil, infraerrors.BadRequest("PROXY_BACKUP_REQUIRED", "backup proxy required when fallback_mode=proxy")
|
||||
}
|
||||
if input.ExpiryWarnDays < 0 {
|
||||
return nil, infraerrors.BadRequest("PROXY_WARN_DAYS_INVALID", "expiry_warn_days must be >= 0")
|
||||
}
|
||||
|
||||
proxy, err := s.proxyRepo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -3084,6 +3133,11 @@ func (s *adminServiceImpl) UpdateProxy(ctx context.Context, id int64, input *Upd
|
||||
if input.Status != "" {
|
||||
proxy.Status = input.Status
|
||||
}
|
||||
// 透传有效期与回退字段
|
||||
proxy.ExpiresAt = input.ExpiresAt
|
||||
proxy.FallbackMode = mode
|
||||
proxy.BackupProxyID = input.BackupProxyID
|
||||
proxy.ExpiryWarnDays = input.ExpiryWarnDays
|
||||
|
||||
if err := s.proxyRepo.Update(ctx, proxy); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -325,6 +325,18 @@ func (s *proxyRepoStub) CountAccountsByProxyID(ctx context.Context, proxyID int6
|
||||
func (s *proxyRepoStub) ListAccountSummariesByProxyID(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error) {
|
||||
panic("unexpected ListAccountSummariesByProxyID call")
|
||||
}
|
||||
func (s *proxyRepoStub) SweepExpiredProxies(_ context.Context, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (s *proxyRepoStub) ListAllForFallback(_ context.Context) ([]Proxy, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (s *proxyRepoStub) CountExpired(_ context.Context) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
func (s *proxyRepoStub) CountExpiringSoon(_ context.Context, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type redeemRepoStub struct {
|
||||
deleteErrByID map[int64]error
|
||||
|
||||
@@ -195,6 +195,10 @@ func (m *mockAccountRepoForPlatform) ResetQuotaUsed(ctx context.Context, id int6
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountRepoForPlatform) RevertProxyFallback(ctx context.Context, accountID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify interface implementation
|
||||
var _ AccountRepository = (*mockAccountRepoForPlatform)(nil)
|
||||
|
||||
|
||||
@@ -184,6 +184,10 @@ func (m *mockAccountRepoForGemini) ResetQuotaUsed(ctx context.Context, id int64)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockAccountRepoForGemini) RevertProxyFallback(ctx context.Context, accountID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify interface implementation
|
||||
var _ AccountRepository = (*mockAccountRepoForGemini)(nil)
|
||||
|
||||
|
||||
@@ -805,6 +805,18 @@ func (m *mockGeminiProxyRepo) CountAccountsByProxyID(ctx context.Context, proxyI
|
||||
func (m *mockGeminiProxyRepo) ListAccountSummariesByProxyID(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error) {
|
||||
panic("not impl")
|
||||
}
|
||||
func (m *mockGeminiProxyRepo) SweepExpiredProxies(ctx context.Context, now time.Time) (int64, error) {
|
||||
panic("not impl")
|
||||
}
|
||||
func (m *mockGeminiProxyRepo) ListAllForFallback(ctx context.Context) ([]Proxy, error) {
|
||||
panic("not impl")
|
||||
}
|
||||
func (m *mockGeminiProxyRepo) CountExpired(ctx context.Context) (int64, error) {
|
||||
panic("not impl")
|
||||
}
|
||||
func (m *mockGeminiProxyRepo) CountExpiringSoon(ctx context.Context, now time.Time) (int64, error) {
|
||||
panic("not impl")
|
||||
}
|
||||
|
||||
// mockDriveClient implements geminicli.DriveClient for tests.
|
||||
type mockDriveClient struct {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/oauth"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
@@ -96,6 +97,18 @@ func (m *mockProxyRepoForOAuth) CountAccountsByProxyID(ctx context.Context, prox
|
||||
func (m *mockProxyRepoForOAuth) ListAccountSummariesByProxyID(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error) {
|
||||
panic("ListAccountSummariesByProxyID not implemented")
|
||||
}
|
||||
func (m *mockProxyRepoForOAuth) SweepExpiredProxies(ctx context.Context, now time.Time) (int64, error) {
|
||||
panic("SweepExpiredProxies not implemented")
|
||||
}
|
||||
func (m *mockProxyRepoForOAuth) ListAllForFallback(ctx context.Context) ([]Proxy, error) {
|
||||
panic("ListAllForFallback not implemented")
|
||||
}
|
||||
func (m *mockProxyRepoForOAuth) CountExpired(ctx context.Context) (int64, error) {
|
||||
panic("CountExpired not implemented")
|
||||
}
|
||||
func (m *mockProxyRepoForOAuth) CountExpiringSoon(ctx context.Context, now time.Time) (int64, error) {
|
||||
panic("CountExpiringSoon not implemented")
|
||||
}
|
||||
|
||||
// =====================
|
||||
// 测试用例
|
||||
|
||||
@@ -35,6 +35,7 @@ type OpsAlertEvaluatorService struct {
|
||||
opsService *OpsService
|
||||
opsRepo OpsRepository
|
||||
emailService *EmailService
|
||||
proxyRepo ProxyRepository
|
||||
|
||||
redisClient *redis.Client
|
||||
cfg *config.Config
|
||||
@@ -67,11 +68,13 @@ func NewOpsAlertEvaluatorService(
|
||||
emailService *EmailService,
|
||||
redisClient *redis.Client,
|
||||
cfg *config.Config,
|
||||
proxyRepo ProxyRepository,
|
||||
) *OpsAlertEvaluatorService {
|
||||
return &OpsAlertEvaluatorService{
|
||||
opsService: opsService,
|
||||
opsRepo: opsRepo,
|
||||
emailService: emailService,
|
||||
proxyRepo: proxyRepo,
|
||||
redisClient: redisClient,
|
||||
cfg: cfg,
|
||||
instanceID: uuid.NewString(),
|
||||
@@ -560,6 +563,24 @@ func (s *OpsAlertEvaluatorService) computeRuleMetric(
|
||||
return float64(countAccountsByCondition(availability.Accounts, func(acc *AccountAvailability) bool {
|
||||
return acc.IsOverloaded
|
||||
})), true
|
||||
case "proxy_expired_count":
|
||||
if s == nil || s.proxyRepo == nil {
|
||||
return 0, false
|
||||
}
|
||||
n, err := s.proxyRepo.CountExpired(ctx)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return float64(n), true
|
||||
case "proxy_expiring_soon_count":
|
||||
if s == nil || s.proxyRepo == nil {
|
||||
return 0, false
|
||||
}
|
||||
n, err := s.proxyRepo.CountExpiringSoon(ctx, time.Now())
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return float64(n), true
|
||||
}
|
||||
|
||||
overview, err := s.opsRepo.GetDashboardOverview(ctx, &OpsDashboardFilter{
|
||||
|
||||
@@ -7,23 +7,38 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
FallbackModeNone = "none"
|
||||
FallbackModeProxy = "proxy"
|
||||
FallbackModeDirect = "direct"
|
||||
)
|
||||
|
||||
type Proxy struct {
|
||||
ID int64
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID int64
|
||||
Name string
|
||||
Protocol string
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ExpiresAt *time.Time
|
||||
FallbackMode string
|
||||
BackupProxyID *int64
|
||||
ExpiryWarnDays int
|
||||
}
|
||||
|
||||
func (p *Proxy) IsActive() bool {
|
||||
return p.Status == StatusActive
|
||||
}
|
||||
|
||||
// IsExpired 报告代理是否已过期(基于 expires_at,与 status 无关)。
|
||||
func (p *Proxy) IsExpired(now time.Time) bool {
|
||||
return p.ExpiresAt != nil && !p.ExpiresAt.After(now)
|
||||
}
|
||||
|
||||
func (p *Proxy) URL() string {
|
||||
u := &url.URL{
|
||||
Scheme: p.Protocol,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProxyExpiryService 周期扫描到期代理并把绑定账号改投备用/直连。
|
||||
type ProxyExpiryService struct {
|
||||
proxyRepo ProxyRepository
|
||||
interval time.Duration
|
||||
stopCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewProxyExpiryService(proxyRepo ProxyRepository, interval time.Duration) *ProxyExpiryService {
|
||||
return &ProxyExpiryService{proxyRepo: proxyRepo, interval: interval, stopCh: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (s *ProxyExpiryService) Start() {
|
||||
if s == nil || s.proxyRepo == 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 *ProxyExpiryService) Stop() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.stopOnce.Do(func() { close(s.stopCh) })
|
||||
s.wg.Wait()
|
||||
}
|
||||
|
||||
func (s *ProxyExpiryService) runOnce() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
changed, err := s.proxyRepo.SweepExpiredProxies(ctx, time.Now())
|
||||
if err != nil {
|
||||
log.Printf("[ProxyExpiry] sweep expired proxies failed: %v", err)
|
||||
return
|
||||
}
|
||||
if changed > 0 {
|
||||
log.Printf("[ProxyExpiry] re-routed %d accounts off expired proxies", changed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import "time"
|
||||
|
||||
// ResolveProxyFallbackTarget 计算一个过期代理 start 应把账号改投到哪里。
|
||||
// 返回 (targetID, change):
|
||||
// - change=false:不改动账号(mode=none,或链路成环/无解的兜底)
|
||||
// - change=true, targetID=nil:改投为直连
|
||||
// - change=true, targetID!=nil:改投到该备用代理 id
|
||||
//
|
||||
// byID 是「全部代理」的快照(id -> Proxy),now 为判定基准时间。
|
||||
func ResolveProxyFallbackTarget(start Proxy, byID map[int64]Proxy, now time.Time) (*int64, bool) {
|
||||
switch start.FallbackMode {
|
||||
case FallbackModeDirect:
|
||||
return nil, true
|
||||
case FallbackModeProxy:
|
||||
visited := map[int64]struct{}{start.ID: {}}
|
||||
curID := start.BackupProxyID
|
||||
for {
|
||||
if curID == nil {
|
||||
return nil, false
|
||||
}
|
||||
if _, seen := visited[*curID]; seen {
|
||||
return nil, false
|
||||
}
|
||||
p, ok := byID[*curID]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if !(&p).IsExpired(now) && p.Status != StatusExpired {
|
||||
id := p.ID
|
||||
return &id, true
|
||||
}
|
||||
visited[*curID] = struct{}{}
|
||||
switch p.FallbackMode {
|
||||
case FallbackModeDirect:
|
||||
return nil, true
|
||||
case FallbackModeProxy:
|
||||
curID = p.BackupProxyID
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mkProxy(id int64, mode string, backup *int64, expiresInDays *int, now time.Time) Proxy {
|
||||
p := Proxy{ID: id, FallbackMode: mode, BackupProxyID: backup}
|
||||
if expiresInDays != nil {
|
||||
t := now.AddDate(0, 0, *expiresInDays)
|
||||
p.ExpiresAt = &t
|
||||
}
|
||||
return p
|
||||
}
|
||||
func i64(v int64) *int64 { return &v }
|
||||
func di(v int) *int { return &v }
|
||||
|
||||
func TestResolveFallbackTarget(t *testing.T) {
|
||||
now := time.Now()
|
||||
t.Run("none keeps original", func(t *testing.T) {
|
||||
a := mkProxy(1, FallbackModeNone, nil, di(-1), now)
|
||||
by := map[int64]Proxy{1: a}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.False(t, change)
|
||||
require.Nil(t, target)
|
||||
})
|
||||
t.Run("direct -> nil target, change", func(t *testing.T) {
|
||||
a := mkProxy(1, FallbackModeDirect, nil, di(-1), now)
|
||||
by := map[int64]Proxy{1: a}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.True(t, change)
|
||||
require.Nil(t, target)
|
||||
})
|
||||
t.Run("proxy -> healthy backup", func(t *testing.T) {
|
||||
b := mkProxy(2, FallbackModeNone, nil, di(30), now)
|
||||
a := mkProxy(1, FallbackModeProxy, i64(2), di(-1), now)
|
||||
by := map[int64]Proxy{1: a, 2: b}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.True(t, change)
|
||||
require.NotNil(t, target)
|
||||
require.Equal(t, int64(2), *target)
|
||||
})
|
||||
t.Run("chain A->B(expired)->C(healthy)", func(t *testing.T) {
|
||||
c := mkProxy(3, FallbackModeNone, nil, di(30), now)
|
||||
b := mkProxy(2, FallbackModeProxy, i64(3), di(-1), now)
|
||||
a := mkProxy(1, FallbackModeProxy, i64(2), di(-1), now)
|
||||
by := map[int64]Proxy{1: a, 2: b, 3: c}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.True(t, change)
|
||||
require.Equal(t, int64(3), *target)
|
||||
})
|
||||
t.Run("cycle A->B->A keeps original", func(t *testing.T) {
|
||||
b := mkProxy(2, FallbackModeProxy, i64(1), di(-1), now)
|
||||
a := mkProxy(1, FallbackModeProxy, i64(2), di(-1), now)
|
||||
by := map[int64]Proxy{1: a, 2: b}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.False(t, change)
|
||||
require.Nil(t, target)
|
||||
})
|
||||
t.Run("chain tail direct fallback", func(t *testing.T) {
|
||||
b := mkProxy(2, FallbackModeDirect, nil, di(-1), now)
|
||||
a := mkProxy(1, FallbackModeProxy, i64(2), di(-1), now)
|
||||
by := map[int64]Proxy{1: a, 2: b}
|
||||
target, change := ResolveProxyFallbackTarget(a, by, now)
|
||||
require.True(t, change)
|
||||
require.Nil(t, target)
|
||||
})
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
@@ -29,6 +30,11 @@ type ProxyRepository interface {
|
||||
ExistsByHostPortAuth(ctx context.Context, host string, port int, username, password string) (bool, error)
|
||||
CountAccountsByProxyID(ctx context.Context, proxyID int64) (int64, error)
|
||||
ListAccountSummariesByProxyID(ctx context.Context, proxyID int64) ([]ProxyAccountSummary, error)
|
||||
|
||||
SweepExpiredProxies(ctx context.Context, now time.Time) (changed int64, err error)
|
||||
ListAllForFallback(ctx context.Context) ([]Proxy, error)
|
||||
CountExpired(ctx context.Context) (int64, error)
|
||||
CountExpiringSoon(ctx context.Context, now time.Time) (int64, error)
|
||||
}
|
||||
|
||||
// CreateProxyRequest 创建代理请求
|
||||
|
||||
@@ -153,6 +153,9 @@ func (m *sessionWindowMockRepo) IncrementQuotaUsed(context.Context, int64, float
|
||||
panic("unexpected")
|
||||
}
|
||||
func (m *sessionWindowMockRepo) ResetQuotaUsed(context.Context, int64) error { panic("unexpected") }
|
||||
func (m *sessionWindowMockRepo) RevertProxyFallback(context.Context, int64) error {
|
||||
panic("unexpected")
|
||||
}
|
||||
|
||||
// newRateLimitServiceForTest creates a RateLimitService with the given mock repo.
|
||||
func newRateLimitServiceForTest(repo AccountRepository) *RateLimitService {
|
||||
|
||||
@@ -164,6 +164,13 @@ func ProvideAccountExpiryService(accountRepo AccountRepository) *AccountExpirySe
|
||||
return svc
|
||||
}
|
||||
|
||||
// ProvideProxyExpiryService creates and starts ProxyExpiryService.
|
||||
func ProvideProxyExpiryService(proxyRepo ProxyRepository) *ProxyExpiryService {
|
||||
svc := NewProxyExpiryService(proxyRepo, time.Minute)
|
||||
svc.Start()
|
||||
return svc
|
||||
}
|
||||
|
||||
// ProvideSubscriptionExpiryService creates and starts SubscriptionExpiryService.
|
||||
func ProvideSubscriptionExpiryService(userSubRepo UserSubscriptionRepository, settingRepo SettingRepository, notificationEmailService *NotificationEmailService, lockCache LeaderLockCache, db *sql.DB) *SubscriptionExpiryService {
|
||||
svc := NewSubscriptionExpiryService(userSubRepo, time.Minute)
|
||||
@@ -281,8 +288,9 @@ func ProvideOpsAlertEvaluatorService(
|
||||
emailService *EmailService,
|
||||
redisClient *redis.Client,
|
||||
cfg *config.Config,
|
||||
proxyRepo ProxyRepository,
|
||||
) *OpsAlertEvaluatorService {
|
||||
svc := NewOpsAlertEvaluatorService(opsService, opsRepo, emailService, redisClient, cfg)
|
||||
svc := NewOpsAlertEvaluatorService(opsService, opsRepo, emailService, redisClient, cfg, proxyRepo)
|
||||
svc.Start()
|
||||
return svc
|
||||
}
|
||||
@@ -555,6 +563,7 @@ var ProviderSet = wire.NewSet(
|
||||
ProvideUpdateService,
|
||||
ProvideTokenRefreshService,
|
||||
ProvideAccountExpiryService,
|
||||
ProvideProxyExpiryService,
|
||||
ProvideSubscriptionExpiryService,
|
||||
ProvideTimingWheelService,
|
||||
ProvideDashboardAggregationService,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- proxies: 有效期 + 失败回退
|
||||
ALTER TABLE proxies ADD COLUMN IF NOT EXISTS expires_at timestamptz;
|
||||
ALTER TABLE proxies ADD COLUMN IF NOT EXISTS fallback_mode varchar(20) NOT NULL DEFAULT 'none';
|
||||
ALTER TABLE proxies ADD COLUMN IF NOT EXISTS backup_proxy_id BIGINT REFERENCES proxies(id) ON DELETE SET NULL;
|
||||
ALTER TABLE proxies ADD COLUMN IF NOT EXISTS expiry_warn_days INT NOT NULL DEFAULT 7;
|
||||
CREATE INDEX IF NOT EXISTS proxies_expires_at_idx ON proxies (expires_at);
|
||||
CREATE INDEX IF NOT EXISTS proxies_backup_proxy_id_idx ON proxies (backup_proxy_id);
|
||||
|
||||
-- accounts: fallback 来源(手动回切用)
|
||||
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS proxy_fallback_origin_id BIGINT;
|
||||
CREATE INDEX IF NOT EXISTS accounts_proxy_fallback_origin_id_idx ON accounts (proxy_fallback_origin_id);
|
||||
@@ -659,6 +659,16 @@ export interface BatchOperationResult {
|
||||
warnings?: Array<{ account_id: number; warning: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert account proxy to original before fallback
|
||||
* @param id - Account ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function revertProxyFallback(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>(`/admin/accounts/${id}/revert-proxy-fallback`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch clear account errors
|
||||
* @param accountIds - Array of account IDs
|
||||
@@ -735,7 +745,8 @@ export const accountsAPI = {
|
||||
getAntigravityDefaultModelMapping,
|
||||
batchClearError,
|
||||
batchRefresh,
|
||||
setPrivacy
|
||||
setPrivacy,
|
||||
revertProxyFallback
|
||||
}
|
||||
|
||||
export default accountsAPI
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function list(
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
protocol?: string
|
||||
status?: 'active' | 'inactive'
|
||||
status?: 'active' | 'inactive' | 'expired'
|
||||
search?: string
|
||||
sort_by?: string
|
||||
sort_order?: 'asc' | 'desc'
|
||||
@@ -227,7 +227,7 @@ export async function exportData(options?: {
|
||||
ids?: number[]
|
||||
filters?: {
|
||||
protocol?: string
|
||||
status?: 'active' | 'inactive'
|
||||
status?: 'active' | 'inactive' | 'expired'
|
||||
search?: string
|
||||
sort_by?: string
|
||||
sort_order?: 'asc' | 'desc'
|
||||
|
||||
@@ -3300,6 +3300,11 @@ export default {
|
||||
recoverStateHint: 'Used to recover error, rate-limit, and temporary unschedulable runtime state.',
|
||||
recoverStateSuccess: 'Account state recovered successfully',
|
||||
recoverStateFailed: 'Failed to recover account state',
|
||||
fallbackActive: 'Fallback',
|
||||
fallbackActiveTip: 'Origin proxy {origin} expired',
|
||||
revertProxy: 'Revert proxy',
|
||||
revertProxySuccess: 'Successfully reverted to original proxy',
|
||||
revertProxyFailed: 'Failed to revert proxy',
|
||||
resetStatus: 'Reset Status',
|
||||
statusReset: 'Account status reset successfully',
|
||||
failedToResetStatus: 'Failed to reset account status',
|
||||
@@ -4176,6 +4181,8 @@ export default {
|
||||
status: 'Status',
|
||||
accounts: 'Accounts',
|
||||
latency: 'Latency',
|
||||
expiry: 'Validity',
|
||||
createdAt: 'Created',
|
||||
actions: 'Actions'
|
||||
},
|
||||
testConnection: 'Test Connection',
|
||||
@@ -4273,7 +4280,21 @@ export default {
|
||||
hostRequired: 'Please enter host address',
|
||||
portInvalid: 'Port must be between 1-65535',
|
||||
deleteConfirm:
|
||||
"Are you sure you want to delete '{name}'? Accounts using this proxy will have their proxy removed."
|
||||
"Are you sure you want to delete '{name}'? Accounts using this proxy will have their proxy removed.",
|
||||
neverExpires: 'Never',
|
||||
expired: 'Expired',
|
||||
overdueDays: 'Overdue {days}d',
|
||||
expiringInDays: 'Expires in {days}d',
|
||||
remainingDays: '{days}d left',
|
||||
expiresAt: 'Validity',
|
||||
nDays: '{days}d',
|
||||
expiryDaysPlaceholder: 'Custom days, empty = never',
|
||||
expiryWarnDays: 'Expiry warning (days)',
|
||||
fallbackMode: 'Failure fallback',
|
||||
fallbackNone: 'No fallback',
|
||||
fallbackProxy: 'Backup proxy',
|
||||
fallbackDirect: 'Direct connection',
|
||||
backupProxy: 'Backup proxy',
|
||||
},
|
||||
|
||||
// Redeem Codes
|
||||
|
||||
@@ -3446,6 +3446,11 @@ export default {
|
||||
recoverStateHint: '用于恢复错误、限流和临时不可调度等可恢复状态。',
|
||||
recoverStateSuccess: '账号状态已恢复',
|
||||
recoverStateFailed: '恢复账号状态失败',
|
||||
fallbackActive: '已回退',
|
||||
fallbackActiveTip: '原代理 {origin} 已到期,当前使用备用代理',
|
||||
revertProxy: '切回原代理',
|
||||
revertProxySuccess: '已成功切回原代理',
|
||||
revertProxyFailed: '切回原代理失败',
|
||||
resetStatus: '重置状态',
|
||||
statusReset: '账号状态已重置',
|
||||
failedToResetStatus: '重置账号状态失败',
|
||||
@@ -4259,6 +4264,8 @@ export default {
|
||||
status: '状态',
|
||||
accounts: '账号数',
|
||||
latency: '延迟',
|
||||
expiry: '有效期',
|
||||
createdAt: '创建时间',
|
||||
actions: '操作',
|
||||
nameLabel: '名称',
|
||||
namePlaceholder: '请输入代理名称',
|
||||
@@ -4394,7 +4401,21 @@ export default {
|
||||
nameRequired: '请输入代理名称',
|
||||
hostRequired: '请输入主机地址',
|
||||
portInvalid: '端口必须在 1-65535 之间',
|
||||
deleteConfirm: "确定要删除代理 '{name}' 吗?使用此代理的账号将被移除代理设置。"
|
||||
deleteConfirm: "确定要删除代理 '{name}' 吗?使用此代理的账号将被移除代理设置。",
|
||||
neverExpires: '永不过期',
|
||||
expired: '已过期',
|
||||
overdueDays: '已超期 {days} 天',
|
||||
expiringInDays: '{days} 天后到期',
|
||||
remainingDays: '剩余 {days} 天',
|
||||
expiresAt: '有效期',
|
||||
nDays: '{days} 天',
|
||||
expiryDaysPlaceholder: '自定义天数,留空 = 永不过期',
|
||||
expiryWarnDays: '到期提醒提前天数',
|
||||
fallbackMode: '失败回退',
|
||||
fallbackNone: '不回退',
|
||||
fallbackProxy: '指定备用代理',
|
||||
fallbackDirect: '回退直连',
|
||||
backupProxy: '备用代理',
|
||||
},
|
||||
|
||||
// Redeem Codes Management
|
||||
|
||||
@@ -711,7 +711,7 @@ export interface Proxy {
|
||||
port: number
|
||||
username: string | null
|
||||
password?: string | null
|
||||
status: 'active' | 'inactive'
|
||||
status: 'active' | 'inactive' | 'expired'
|
||||
account_count?: number // Number of accounts using this proxy
|
||||
latency_ms?: number
|
||||
latency_status?: 'success' | 'failed'
|
||||
@@ -726,6 +726,10 @@ export interface Proxy {
|
||||
quality_grade?: string
|
||||
quality_summary?: string
|
||||
quality_checked?: number
|
||||
expires_at: string | null
|
||||
fallback_mode: 'none' | 'proxy' | 'direct'
|
||||
backup_proxy_id?: number | null
|
||||
expiry_warn_days: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -831,6 +835,8 @@ export interface Account {
|
||||
antigravity_credits_overages?: Record<string, { activated_at: string; active_until: string }>
|
||||
} & Record<string, unknown>)
|
||||
proxy_id: number | null
|
||||
proxy_fallback_origin_id?: number | null
|
||||
proxy_fallback_origin_name?: string | null
|
||||
concurrency: number
|
||||
load_factor?: number | null
|
||||
current_concurrency?: number // Real-time concurrency count from Redis
|
||||
@@ -1079,6 +1085,10 @@ export interface CreateProxyRequest {
|
||||
port: number
|
||||
username?: string | null
|
||||
password?: string | null
|
||||
expires_at?: number | null // unix 秒;null/0 = 永不过期
|
||||
fallback_mode?: 'none' | 'proxy' | 'direct'
|
||||
backup_proxy_id?: number | null
|
||||
expiry_warn_days?: number
|
||||
}
|
||||
|
||||
export interface UpdateProxyRequest {
|
||||
@@ -1089,6 +1099,10 @@ export interface UpdateProxyRequest {
|
||||
username?: string | null
|
||||
password?: string | null
|
||||
status?: 'active' | 'inactive'
|
||||
expires_at?: number | null // unix 秒;null/0 = 永不过期
|
||||
fallback_mode?: 'none' | 'proxy' | 'direct'
|
||||
backup_proxy_id?: number | null
|
||||
expiry_warn_days?: number
|
||||
}
|
||||
|
||||
export interface AdminDataPayload {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { daysUntil, proxyExpiryBadgeClass, proxyExpiryLabelKey } from '../proxyExpiry'
|
||||
|
||||
// 固定「现在」,按天数构造确定输入:isoInDays(n) 距今正好 n 天
|
||||
const NOW = new Date('2026-06-02T00:00:00Z')
|
||||
const isoInDays = (n: number): string => new Date(NOW.getTime() + n * 86400000).toISOString()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(NOW)
|
||||
})
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
describe('daysUntil', () => {
|
||||
it('返回距今整天数', () => {
|
||||
expect(daysUntil(isoInDays(10))).toBe(10)
|
||||
expect(daysUntil(isoInDays(-3))).toBe(-3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('proxyExpiryBadgeClass', () => {
|
||||
it('status=expired → danger', () => {
|
||||
expect(proxyExpiryBadgeClass(isoInDays(30), 'expired')).toBe('badge badge-danger')
|
||||
})
|
||||
it('≤3 天 → danger(含边界 3)', () => {
|
||||
expect(proxyExpiryBadgeClass(isoInDays(2), 'active')).toBe('badge badge-danger')
|
||||
expect(proxyExpiryBadgeClass(isoInDays(3), 'active')).toBe('badge badge-danger')
|
||||
})
|
||||
it('4–7 天 → warning(含边界 7)', () => {
|
||||
expect(proxyExpiryBadgeClass(isoInDays(5), 'active')).toBe('badge badge-warning')
|
||||
expect(proxyExpiryBadgeClass(isoInDays(7), 'active')).toBe('badge badge-warning')
|
||||
})
|
||||
it('>7 天 → gray', () => {
|
||||
expect(proxyExpiryBadgeClass(isoInDays(30), 'active')).toBe('text-gray-500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('proxyExpiryLabelKey', () => {
|
||||
it('status=expired → expired key', () => {
|
||||
expect(proxyExpiryLabelKey(isoInDays(30), 'expired')).toEqual({ key: 'admin.proxies.expired' })
|
||||
})
|
||||
it('已逾期(d<0) → overdueDays', () => {
|
||||
expect(proxyExpiryLabelKey(isoInDays(-3), 'active')).toEqual({
|
||||
key: 'admin.proxies.overdueDays',
|
||||
params: { days: 3 },
|
||||
})
|
||||
})
|
||||
it('≤7 天 → expiringInDays', () => {
|
||||
expect(proxyExpiryLabelKey(isoInDays(5), 'active')).toEqual({
|
||||
key: 'admin.proxies.expiringInDays',
|
||||
params: { days: 5 },
|
||||
})
|
||||
})
|
||||
it('>7 天 → remainingDays', () => {
|
||||
expect(proxyExpiryLabelKey(isoInDays(30), 'active')).toEqual({
|
||||
key: 'admin.proxies.remainingDays',
|
||||
params: { days: 30 },
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
// 代理有效期展示逻辑(ProxiesView 与 AccountsView 共用)。
|
||||
// 到期紧迫度固定两档:剩余 ≤3 天红、≤7 天黄(不读 per-proxy expiry_warn_days)。
|
||||
export const EXPIRY_WARN_DAYS = 7
|
||||
export const EXPIRY_DANGER_DAYS = 3
|
||||
|
||||
// 距今整天数(向上取整)。
|
||||
export const daysUntil = (iso: string): number =>
|
||||
Math.ceil((new Date(iso).getTime() - Date.now()) / 86400000)
|
||||
|
||||
// 倒计时徽章的 CSS class(纯函数,无 i18n 依赖)。
|
||||
export function proxyExpiryBadgeClass(expiresAt: string | null, status?: string): string {
|
||||
if (status === 'expired') return 'badge badge-danger'
|
||||
const d = expiresAt ? daysUntil(expiresAt) : Infinity
|
||||
if (d <= EXPIRY_DANGER_DAYS) return 'badge badge-danger'
|
||||
if (d <= EXPIRY_WARN_DAYS) return 'badge badge-warning'
|
||||
return 'text-gray-500'
|
||||
}
|
||||
|
||||
// 倒计时文案的 i18n key + 参数(返回 key 而非已翻译文本,便于单测且不耦合 i18n)。
|
||||
export function proxyExpiryLabelKey(
|
||||
expiresAt: string | null,
|
||||
status?: string,
|
||||
): { key: string; params?: { days: number } } {
|
||||
if (status === 'expired') return { key: 'admin.proxies.expired' }
|
||||
const d = expiresAt ? daysUntil(expiresAt) : Infinity
|
||||
if (d < 0) return { key: 'admin.proxies.overdueDays', params: { days: Math.abs(d) } }
|
||||
if (d <= EXPIRY_WARN_DAYS) return { key: 'admin.proxies.expiringInDays', params: { days: d } }
|
||||
return { key: 'admin.proxies.remainingDays', params: { days: d } }
|
||||
}
|
||||
@@ -288,13 +288,25 @@
|
||||
/>
|
||||
</template>
|
||||
<template #cell-proxy="{ row }">
|
||||
<div v-if="row.proxy" class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ row.proxy.name }}</span>
|
||||
<span v-if="row.proxy.country_code" class="text-xs text-gray-500 dark:text-gray-400">
|
||||
({{ row.proxy.country_code }})
|
||||
</span>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div v-if="row.proxy" class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ row.proxy.name }}</span>
|
||||
<span v-if="row.proxy.country_code" class="text-xs text-gray-500 dark:text-gray-400">
|
||||
({{ row.proxy.country_code }})
|
||||
</span>
|
||||
</div>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
||||
<div v-if="row.proxy && row.proxy.expires_at" class="flex items-center gap-2 text-xs">
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ formatDateTime(row.proxy.expires_at) }}</span>
|
||||
<span :class="proxyExpiryBadge(row.proxy)">{{ proxyExpiryText(row.proxy) }}</span>
|
||||
</div>
|
||||
<div v-if="row.proxy_fallback_origin_id" class="flex items-center gap-1">
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200" :title="t('admin.accounts.fallbackActiveTip', { origin: row.proxy_fallback_origin_name })">
|
||||
{{ t('admin.accounts.fallbackActive') }}
|
||||
</span>
|
||||
<button class="text-xs px-1.5 py-0.5 rounded border border-gray-300 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700" @click="onRevertFallback(row)">{{ t('admin.accounts.revertProxy') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
||||
</template>
|
||||
<template #cell-rate_multiplier="{ row }">
|
||||
<span class="text-sm font-mono text-gray-700 dark:text-gray-300">
|
||||
@@ -421,6 +433,7 @@ import ErrorPassthroughRulesModal from '@/components/admin/ErrorPassthroughRules
|
||||
import TLSFingerprintProfilesModal from '@/components/admin/TLSFingerprintProfilesModal.vue'
|
||||
import { buildOpenAIUsageRefreshKey } from '@/utils/accountUsageRefresh'
|
||||
import { formatDateTime, formatRelativeTime } from '@/utils/format'
|
||||
import { proxyExpiryBadgeClass, proxyExpiryLabelKey } from '@/utils/proxyExpiry'
|
||||
import type { Account, AccountPlatform, AccountType, Proxy as AccountProxy, AdminGroup, WindowStats, ClaudeModel } from '@/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -1597,6 +1610,16 @@ const handleSetPrivacy = async (a: Account) => {
|
||||
appStore.showError(error?.response?.data?.message || t('admin.accounts.privacyFailed'))
|
||||
}
|
||||
}
|
||||
const onRevertFallback = async (a: Account) => {
|
||||
try {
|
||||
await adminAPI.accounts.revertProxyFallback(a.id)
|
||||
appStore.showSuccess(t('admin.accounts.revertProxySuccess'))
|
||||
reload()
|
||||
} catch (error: any) {
|
||||
console.error('Failed to revert proxy fallback:', error)
|
||||
appStore.showError(error?.response?.data?.message || t('admin.accounts.revertProxyFailed'))
|
||||
}
|
||||
}
|
||||
const handleDelete = (a: Account) => { deletingAcc.value = a; showDeleteDialog.value = true }
|
||||
const confirmDelete = async () => { if(!deletingAcc.value) return; try { await adminAPI.accounts.delete(deletingAcc.value.id); showDeleteDialog.value = false; deletingAcc.value = null; reload() } catch (error) { console.error('Failed to delete account:', error) } }
|
||||
const handleToggleSchedulable = async (a: Account) => {
|
||||
@@ -1639,6 +1662,12 @@ const isExpired = (value: number | null) => {
|
||||
if (!value) return false
|
||||
return value * 1000 <= Date.now()
|
||||
}
|
||||
// 所绑定代理的有效期(逻辑同 /admin/proxies,见 utils/proxyExpiry)
|
||||
const proxyExpiryBadge = (p: AccountProxy): string => proxyExpiryBadgeClass(p.expires_at, p.status)
|
||||
const proxyExpiryText = (p: AccountProxy): string => {
|
||||
const { key, params } = proxyExpiryLabelKey(p.expires_at, p.status)
|
||||
return params ? t(key, params) : t(key)
|
||||
}
|
||||
|
||||
// 滚动时关闭操作菜单(不关闭列设置下拉菜单)
|
||||
const handleScroll = () => {
|
||||
|
||||
@@ -244,8 +244,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-expiry="{ row }">
|
||||
<span v-if="!row.expires_at" class="text-sm text-gray-400">{{ t('admin.proxies.neverExpires') }}</span>
|
||||
<div v-else class="flex flex-col text-xs">
|
||||
<span class="text-gray-700 dark:text-gray-200">{{ formatDateTime(row.expires_at) }}</span>
|
||||
<span :class="expiryBadgeClass(row)">{{ expiryLabel(row) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-created_at="{ row }">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-300">{{ formatDateTime(row.created_at) }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-status="{ value }">
|
||||
<span :class="['badge', value === 'active' ? 'badge-success' : 'badge-danger']">
|
||||
<span
|
||||
:class="[
|
||||
'badge',
|
||||
value === 'active' ? 'badge-success' : value === 'expired' ? 'badge-danger' : 'badge-danger'
|
||||
]"
|
||||
>
|
||||
{{ t('admin.accounts.status.' + value) }}
|
||||
</span>
|
||||
</template>
|
||||
@@ -475,6 +492,41 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.proxies.expiresAt') }}</label>
|
||||
<div class="mb-2 flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="d in EXPIRY_PRESETS"
|
||||
:key="d"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="createForm.expires_at === addDaysToBase('', d) ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="createExpiresDays = d"
|
||||
>
|
||||
{{ t('admin.proxies.nDays', { days: d }) }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="createExpiresDays"
|
||||
type="number"
|
||||
min="0"
|
||||
class="input mb-2"
|
||||
:placeholder="t('admin.proxies.expiryDaysPlaceholder')"
|
||||
/>
|
||||
<input v-model="createForm.expires_at" type="date" class="input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.proxies.fallbackMode') }}</label>
|
||||
<Select v-model="createForm.fallback_mode" :options="[
|
||||
{ label: t('admin.proxies.fallbackNone'), value: 'none' },
|
||||
{ label: t('admin.proxies.fallbackProxy'), value: 'proxy' },
|
||||
{ label: t('admin.proxies.fallbackDirect'), value: 'direct' },
|
||||
]" />
|
||||
</div>
|
||||
<div v-if="createForm.fallback_mode === 'proxy'">
|
||||
<label class="input-label">{{ t('admin.proxies.backupProxy') }}</label>
|
||||
<Select v-model="createForm.backup_proxy_id" :options="backupProxyOptions()" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -673,6 +725,41 @@
|
||||
<label class="input-label">{{ t('admin.proxies.status') }}</label>
|
||||
<Select v-model="editForm.status" :options="editStatusOptions" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.proxies.expiresAt') }}</label>
|
||||
<div class="mb-2 flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="d in EXPIRY_PRESETS"
|
||||
:key="d"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="editForm.expires_at === addDaysToBase(editBaseDate, d) ? 'btn-primary' : 'btn-secondary'"
|
||||
@click="editExpiresDays = d"
|
||||
>
|
||||
{{ t('admin.proxies.nDays', { days: d }) }}
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="editExpiresDays"
|
||||
type="number"
|
||||
min="0"
|
||||
class="input mb-2"
|
||||
:placeholder="t('admin.proxies.expiryDaysPlaceholder')"
|
||||
/>
|
||||
<input v-model="editForm.expires_at" type="date" class="input" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.proxies.fallbackMode') }}</label>
|
||||
<Select v-model="editForm.fallback_mode" :options="[
|
||||
{ label: t('admin.proxies.fallbackNone'), value: 'none' },
|
||||
{ label: t('admin.proxies.fallbackProxy'), value: 'proxy' },
|
||||
{ label: t('admin.proxies.fallbackDirect'), value: 'direct' },
|
||||
]" />
|
||||
</div>
|
||||
<div v-if="editForm.fallback_mode === 'proxy'">
|
||||
<label class="input-label">{{ t('admin.proxies.backupProxy') }}</label>
|
||||
<Select v-model="editForm.backup_proxy_id" :options="backupProxyOptions(editingProxy?.id)" />
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -899,6 +986,8 @@ import { useClipboard } from '@/composables/useClipboard'
|
||||
import { useSwipeSelect } from '@/composables/useSwipeSelect'
|
||||
import { useTableSelection } from '@/composables/useTableSelection'
|
||||
import { getPersistedPageSize } from '@/composables/usePersistedPageSize'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { proxyExpiryBadgeClass, proxyExpiryLabelKey } from '@/utils/proxyExpiry'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -913,6 +1002,8 @@ const columns = computed<Column[]>(() => [
|
||||
{ key: 'location', label: t('admin.proxies.columns.location'), sortable: false },
|
||||
{ key: 'account_count', label: t('admin.proxies.columns.accounts'), sortable: true },
|
||||
{ key: 'latency', label: t('admin.proxies.columns.latency'), sortable: false },
|
||||
{ key: 'expiry', label: t('admin.proxies.columns.expiry'), sortable: true },
|
||||
{ key: 'created_at', label: t('admin.proxies.columns.createdAt'), sortable: true },
|
||||
{ key: 'status', label: t('admin.proxies.columns.status'), sortable: true },
|
||||
{ key: 'actions', label: t('admin.proxies.columns.actions'), sortable: false }
|
||||
])
|
||||
@@ -929,7 +1020,8 @@ const protocolOptions = computed(() => [
|
||||
const statusOptions = computed(() => [
|
||||
{ value: '', label: t('admin.proxies.allStatus') },
|
||||
{ value: 'active', label: t('admin.accounts.status.active') },
|
||||
{ value: 'inactive', label: t('admin.accounts.status.inactive') }
|
||||
{ value: 'inactive', label: t('admin.accounts.status.inactive') },
|
||||
{ value: 'expired', label: t('admin.proxies.expired') }
|
||||
])
|
||||
|
||||
// Form options
|
||||
@@ -1035,7 +1127,11 @@ const createForm = reactive({
|
||||
host: '',
|
||||
port: 8080,
|
||||
username: '',
|
||||
password: ''
|
||||
password: '',
|
||||
expires_at: '' as string,
|
||||
fallback_mode: 'none' as 'none' | 'proxy' | 'direct',
|
||||
backup_proxy_id: null as number | null,
|
||||
expiry_warn_days: 7 as number,
|
||||
})
|
||||
|
||||
const editForm = reactive({
|
||||
@@ -1045,9 +1141,22 @@ const editForm = reactive({
|
||||
port: 8080,
|
||||
username: '',
|
||||
password: '',
|
||||
status: 'active' as 'active' | 'inactive'
|
||||
status: 'active' as 'active' | 'inactive' | 'expired',
|
||||
expires_at: '' as string,
|
||||
fallback_mode: 'none' as 'none' | 'proxy' | 'direct',
|
||||
backup_proxy_id: null as number | null,
|
||||
expiry_warn_days: 7 as number,
|
||||
})
|
||||
|
||||
const allProxiesForBackup = ref<Proxy[]>([])
|
||||
const loadBackupProxyOptions = async () => {
|
||||
allProxiesForBackup.value = await adminAPI.proxies.getAllWithCount()
|
||||
}
|
||||
const backupProxyOptions = (excludeId?: number) =>
|
||||
allProxiesForBackup.value
|
||||
.filter(p => p.id !== excludeId)
|
||||
.map(p => ({ label: `${p.name} (${p.host}:${p.port})`, value: p.id }))
|
||||
|
||||
let abortController: AbortController | null = null
|
||||
|
||||
const isAbortError = (error: unknown) => {
|
||||
@@ -1072,7 +1181,7 @@ const toggleSelectAllVisible = (event: Event) => {
|
||||
|
||||
const buildProxyQueryFilters = () => ({
|
||||
protocol: filters.protocol || undefined,
|
||||
status: (filters.status || undefined) as 'active' | 'inactive' | undefined,
|
||||
status: (filters.status || undefined) as 'active' | 'inactive' | 'expired' | undefined,
|
||||
search: searchQuery.value || undefined,
|
||||
sort_by: sortState.sort_by,
|
||||
sort_order: sortState.sort_order
|
||||
@@ -1148,6 +1257,10 @@ const closeCreateModal = () => {
|
||||
createForm.port = 8080
|
||||
createForm.username = ''
|
||||
createForm.password = ''
|
||||
createForm.expires_at = ''
|
||||
createForm.fallback_mode = 'none'
|
||||
createForm.backup_proxy_id = null
|
||||
createForm.expiry_warn_days = 7
|
||||
createPasswordVisible.value = false
|
||||
batchInput.value = ''
|
||||
batchParseResult.total = 0
|
||||
@@ -1272,7 +1385,11 @@ const handleCreateProxy = async () => {
|
||||
host: createForm.host.trim(),
|
||||
port: createForm.port,
|
||||
username: createForm.username.trim() || null,
|
||||
password: createForm.password.trim() || null
|
||||
password: createForm.password.trim() || null,
|
||||
expires_at: createForm.expires_at ? Math.floor(new Date(createForm.expires_at).getTime() / 1000) : null,
|
||||
fallback_mode: createForm.fallback_mode,
|
||||
backup_proxy_id: createForm.fallback_mode === 'proxy' ? createForm.backup_proxy_id : null,
|
||||
expiry_warn_days: createForm.expiry_warn_days,
|
||||
})
|
||||
appStore.showSuccess(t('admin.proxies.proxyCreated'))
|
||||
closeCreateModal()
|
||||
@@ -1293,7 +1410,11 @@ const handleEdit = (proxy: Proxy) => {
|
||||
editForm.port = proxy.port
|
||||
editForm.username = proxy.username || ''
|
||||
editForm.password = proxy.password || ''
|
||||
editForm.status = proxy.status
|
||||
editForm.status = proxy.status === 'expired' ? 'inactive' : proxy.status
|
||||
editForm.expires_at = proxy.expires_at ? proxy.expires_at.slice(0, 10) : ''
|
||||
editForm.fallback_mode = proxy.fallback_mode || 'none'
|
||||
editForm.backup_proxy_id = proxy.backup_proxy_id ?? null
|
||||
editForm.expiry_warn_days = proxy.expiry_warn_days ?? 7
|
||||
editPasswordVisible.value = false
|
||||
editPasswordDirty.value = false
|
||||
showEditModal.value = true
|
||||
@@ -1329,7 +1450,11 @@ const handleUpdateProxy = async () => {
|
||||
host: editForm.host.trim(),
|
||||
port: editForm.port,
|
||||
username: editForm.username.trim() || null,
|
||||
status: editForm.status
|
||||
status: editForm.status,
|
||||
expires_at: editForm.expires_at ? Math.floor(new Date(editForm.expires_at).getTime() / 1000) : null,
|
||||
fallback_mode: editForm.fallback_mode,
|
||||
backup_proxy_id: editForm.fallback_mode === 'proxy' ? editForm.backup_proxy_id : null,
|
||||
expiry_warn_days: editForm.expiry_warn_days,
|
||||
}
|
||||
|
||||
// Only include password if user actually modified the field
|
||||
@@ -1575,6 +1700,59 @@ const qualityStatusLabel = (status: string) => {
|
||||
return t('admin.proxies.qualityStatusFail')
|
||||
}
|
||||
|
||||
// 有效期「选天数」⇄ 日历联动:天数自 base 起算(创建=今天;编辑=代理创建日),本地日历日 round-trip 稳定;canonical 仍是 expires_at 日期串
|
||||
const EXPIRY_PRESETS = [7, 30, 90, 180]
|
||||
const toLocalDateStr = (dt: Date): string => {
|
||||
const y = dt.getFullYear()
|
||||
const m = String(dt.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(dt.getDate()).padStart(2, '0')
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
// base 为空 → 今天本地 00:00;否则该日期本地 00:00
|
||||
const baseDateOrToday = (baseDateStr: string): Date => {
|
||||
const base = baseDateStr ? new Date(`${baseDateStr}T00:00:00`) : new Date()
|
||||
base.setHours(0, 0, 0, 0)
|
||||
return base
|
||||
}
|
||||
// base + N 天 → 本地 YYYY-MM-DD;N≤0/空 → '' 表示永不过期
|
||||
const addDaysToBase = (baseDateStr: string, n: number | null): string => {
|
||||
const days = Number(n)
|
||||
if (!days || days <= 0) return ''
|
||||
const dt = baseDateOrToday(baseDateStr)
|
||||
dt.setDate(dt.getDate() + days)
|
||||
return toLocalDateStr(dt)
|
||||
}
|
||||
// target 相对 base 的整天数(本地日历差,避免时区/时刻抖动)
|
||||
const daysFromBase = (baseDateStr: string, targetDateStr: string): number | null => {
|
||||
if (!targetDateStr) return null
|
||||
const target = new Date(`${targetDateStr}T00:00:00`)
|
||||
return Math.round((target.getTime() - baseDateOrToday(baseDateStr).getTime()) / 86400000)
|
||||
}
|
||||
// 编辑时有效期自「代理创建日」起算;创建时无 created_at → base='' 用今天
|
||||
const editBaseDate = computed(() =>
|
||||
editingProxy.value?.created_at ? editingProxy.value.created_at.slice(0, 10) : '',
|
||||
)
|
||||
const createExpiresDays = computed<number | null>({
|
||||
get: () => daysFromBase('', createForm.expires_at),
|
||||
set: (v) => {
|
||||
createForm.expires_at = addDaysToBase('', v)
|
||||
},
|
||||
})
|
||||
const editExpiresDays = computed<number | null>({
|
||||
get: () => daysFromBase(editBaseDate.value, editForm.expires_at),
|
||||
set: (v) => {
|
||||
editForm.expires_at = addDaysToBase(editBaseDate.value, v)
|
||||
},
|
||||
})
|
||||
|
||||
const expiryLabel = (row: Proxy): string => {
|
||||
const { key, params } = proxyExpiryLabelKey(row.expires_at, row.status)
|
||||
return params ? t(key, params) : t(key)
|
||||
}
|
||||
|
||||
const expiryBadgeClass = (row: Proxy): string =>
|
||||
proxyExpiryBadgeClass(row.expires_at, row.status)
|
||||
|
||||
const qualityOverallClass = (status?: string) => {
|
||||
if (status === 'healthy') return 'badge-success'
|
||||
if (status === 'warn') return 'badge-warning'
|
||||
@@ -1877,6 +2055,7 @@ function closeCopyMenu() {
|
||||
|
||||
onMounted(() => {
|
||||
loadProxies()
|
||||
loadBackupProxyOptions()
|
||||
document.addEventListener('click', closeCopyMenu)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user