mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-04 21:20:51 +08:00
feat(monitor): admin channel monitor MVP with SSRF protection and batch aggregation
新增 admin「渠道监控」模块(参考 BingZi-233/check-cx),独立于现有 Channel 体系。
admin 配置 + 后台定时调用上游 LLM chat completions 健康检查 + 所有登录用户只读可见。
后端:
- ent: channel_monitor + channel_monitor_history(AES-256-GCM 加密 api_key)
- service 按职责拆分:service/aggregator/validate/checker/runner/ssrf
- provider strategy map 替代 switch(openai/anthropic/gemini)
- repository batch 聚合(ListLatestForMonitorIDs + ComputeAvailabilityForMonitors)消除 N+1
- runner: ticker(5s) + pond worker pool(5) + inFlight 防并发 + TrySubmit 防雪崩
+ 凌晨 3 点 cron 清理 30 天历史
- SSRF 防护:强制 https + 私网/loopback/云元数据 IP 拒绝(127/8、10/8、172.16/12、
192.168/16、169.254/16、100.64/10、::1、fc00::/7、fe80::/10)+ DialContext
在 socket 层防 DNS rebinding
- API key sanitize:擦除 url.Error 与上游响应 body 中的 sk-/sk-ant-/AIza/JWT 模式
- APIKeyDecryptFailed 标志位 + 单 monitor 路径检测,避免空 key 调用上游
handler:
- admin: CRUD + 手动触发 + 历史接口(api_key 脱敏)
- user: 只读列表 + 状态详情(去除 api_key/endpoint)
- ParseChannelMonitorID 共用 + dto.ChannelMonitorExtraModelStatus 共用
前端:
- 路由 /admin/channels/{pricing,monitor} + /monitor(用户只读)
- AppSidebar 父项 expandOnly 支持
- ChannelMonitorView 拆为 8 个子组件 + ChannelStatusView 拆出 detail dialog
- composables/useChannelMonitorFormat + constants/channelMonitor 共享
- i18n monitorCommon namespace 消除 admin/user 两 view 重复
合规:所有文件符合 CLAUDE.md(Go ≤ 500 行 / Vue ≤ 300 行 / 函数 ≤ 30 行)
CI: go build / gofmt / golangci-lint(0 issues) / make test-unit / pnpm build 全绿
This commit is contained in:
@@ -210,6 +210,16 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
scheduledTestService := service.ProvideScheduledTestService(scheduledTestPlanRepository, scheduledTestResultRepository)
|
||||
scheduledTestHandler := admin.NewScheduledTestHandler(scheduledTestService)
|
||||
channelHandler := admin.NewChannelHandler(channelService, billingService)
|
||||
sqlDB, err := repository.ProvideSQLDB(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
channelMonitorRepository := repository.NewChannelMonitorRepository(client, sqlDB)
|
||||
channelMonitorService := service.ProvideChannelMonitorService(channelMonitorRepository, secretEncryptor)
|
||||
channelMonitorHandler := admin.NewChannelMonitorHandler(channelMonitorService)
|
||||
channelMonitorUserHandler := handler.NewChannelMonitorUserHandler(channelMonitorService)
|
||||
channelMonitorRunner := service.ProvideChannelMonitorRunner(channelMonitorService)
|
||||
_ = channelMonitorRunner
|
||||
registry := payment.ProvideRegistry()
|
||||
encryptionKey, err := payment.ProvideEncryptionKey(configConfig)
|
||||
if err != nil {
|
||||
@@ -221,7 +231,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService)
|
||||
paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService)
|
||||
paymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService)
|
||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, paymentHandler)
|
||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, paymentHandler)
|
||||
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
|
||||
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
|
||||
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
|
||||
@@ -233,7 +243,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
paymentWebhookHandler := handler.NewPaymentWebhookHandler(paymentService, registry)
|
||||
idempotencyCoordinator := service.ProvideIdempotencyCoordinator(idempotencyRepository, configConfig)
|
||||
idempotencyCleanupService := service.ProvideIdempotencyCleanupService(idempotencyRepository, configConfig)
|
||||
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, idempotencyCoordinator, idempotencyCleanupService)
|
||||
handlers := handler.ProvideHandlers(authHandler, userHandler, apiKeyHandler, usageHandler, redeemHandler, subscriptionHandler, announcementHandler, channelMonitorUserHandler, adminHandlers, gatewayHandler, openAIGatewayHandler, handlerSettingHandler, totpHandler, handlerPaymentHandler, paymentWebhookHandler, idempotencyCoordinator, idempotencyCleanupService)
|
||||
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
||||
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
||||
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
||||
|
||||
273
backend/ent/channelmonitor.go
Normal file
273
backend/ent/channelmonitor.go
Normal file
@@ -0,0 +1,273 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
)
|
||||
|
||||
// ChannelMonitor is the model entity for the ChannelMonitor schema.
|
||||
type ChannelMonitor struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID int64 `json:"id,omitempty"`
|
||||
// CreatedAt holds the value of the "created_at" field.
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// UpdatedAt holds the value of the "updated_at" field.
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
// Name holds the value of the "name" field.
|
||||
Name string `json:"name,omitempty"`
|
||||
// Provider holds the value of the "provider" field.
|
||||
Provider channelmonitor.Provider `json:"provider,omitempty"`
|
||||
// Provider base origin, e.g. https://api.openai.com
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
// AES-256-GCM encrypted API key
|
||||
APIKeyEncrypted string `json:"-"`
|
||||
// PrimaryModel holds the value of the "primary_model" field.
|
||||
PrimaryModel string `json:"primary_model,omitempty"`
|
||||
// Additional model names to test alongside primary_model
|
||||
ExtraModels []string `json:"extra_models,omitempty"`
|
||||
// GroupName holds the value of the "group_name" field.
|
||||
GroupName string `json:"group_name,omitempty"`
|
||||
// Enabled holds the value of the "enabled" field.
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
// IntervalSeconds holds the value of the "interval_seconds" field.
|
||||
IntervalSeconds int `json:"interval_seconds,omitempty"`
|
||||
// LastCheckedAt holds the value of the "last_checked_at" field.
|
||||
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
|
||||
// CreatedBy holds the value of the "created_by" field.
|
||||
CreatedBy int64 `json:"created_by,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the ChannelMonitorQuery when eager-loading is set.
|
||||
Edges ChannelMonitorEdges `json:"edges"`
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// ChannelMonitorEdges holds the relations/edges for other nodes in the graph.
|
||||
type ChannelMonitorEdges struct {
|
||||
// History holds the value of the history edge.
|
||||
History []*ChannelMonitorHistory `json:"history,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
}
|
||||
|
||||
// HistoryOrErr returns the History value or an error if the edge
|
||||
// was not loaded in eager-loading.
|
||||
func (e ChannelMonitorEdges) HistoryOrErr() ([]*ChannelMonitorHistory, error) {
|
||||
if e.loadedTypes[0] {
|
||||
return e.History, nil
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "history"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*ChannelMonitor) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case channelmonitor.FieldExtraModels:
|
||||
values[i] = new([]byte)
|
||||
case channelmonitor.FieldEnabled:
|
||||
values[i] = new(sql.NullBool)
|
||||
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldCreatedBy:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName:
|
||||
values[i] = new(sql.NullString)
|
||||
case channelmonitor.FieldCreatedAt, channelmonitor.FieldUpdatedAt, channelmonitor.FieldLastCheckedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the ChannelMonitor fields.
|
||||
func (_m *ChannelMonitor) assignValues(columns []string, values []any) error {
|
||||
if m, n := len(values), len(columns); m < n {
|
||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||
}
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case channelmonitor.FieldID:
|
||||
value, ok := values[i].(*sql.NullInt64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field id", value)
|
||||
}
|
||||
_m.ID = int64(value.Int64)
|
||||
case channelmonitor.FieldCreatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field created_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CreatedAt = value.Time
|
||||
}
|
||||
case channelmonitor.FieldUpdatedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field updated_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.UpdatedAt = value.Time
|
||||
}
|
||||
case channelmonitor.FieldName:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field name", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Name = value.String
|
||||
}
|
||||
case channelmonitor.FieldProvider:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field provider", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Provider = channelmonitor.Provider(value.String)
|
||||
}
|
||||
case channelmonitor.FieldEndpoint:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field endpoint", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Endpoint = value.String
|
||||
}
|
||||
case channelmonitor.FieldAPIKeyEncrypted:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field api_key_encrypted", values[i])
|
||||
} else if value.Valid {
|
||||
_m.APIKeyEncrypted = value.String
|
||||
}
|
||||
case channelmonitor.FieldPrimaryModel:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field primary_model", values[i])
|
||||
} else if value.Valid {
|
||||
_m.PrimaryModel = value.String
|
||||
}
|
||||
case channelmonitor.FieldExtraModels:
|
||||
if value, ok := values[i].(*[]byte); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field extra_models", values[i])
|
||||
} else if value != nil && len(*value) > 0 {
|
||||
if err := json.Unmarshal(*value, &_m.ExtraModels); err != nil {
|
||||
return fmt.Errorf("unmarshal field extra_models: %w", err)
|
||||
}
|
||||
}
|
||||
case channelmonitor.FieldGroupName:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field group_name", values[i])
|
||||
} else if value.Valid {
|
||||
_m.GroupName = value.String
|
||||
}
|
||||
case channelmonitor.FieldEnabled:
|
||||
if value, ok := values[i].(*sql.NullBool); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field enabled", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Enabled = value.Bool
|
||||
}
|
||||
case channelmonitor.FieldIntervalSeconds:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field interval_seconds", values[i])
|
||||
} else if value.Valid {
|
||||
_m.IntervalSeconds = int(value.Int64)
|
||||
}
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field last_checked_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.LastCheckedAt = new(time.Time)
|
||||
*_m.LastCheckedAt = value.Time
|
||||
}
|
||||
case channelmonitor.FieldCreatedBy:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field created_by", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CreatedBy = value.Int64
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the ChannelMonitor.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *ChannelMonitor) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QueryHistory queries the "history" edge of the ChannelMonitor entity.
|
||||
func (_m *ChannelMonitor) QueryHistory() *ChannelMonitorHistoryQuery {
|
||||
return NewChannelMonitorClient(_m.config).QueryHistory(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this ChannelMonitor.
|
||||
// Note that you need to call ChannelMonitor.Unwrap() before calling this method if this ChannelMonitor
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *ChannelMonitor) Update() *ChannelMonitorUpdateOne {
|
||||
return NewChannelMonitorClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the ChannelMonitor entity that was returned from a transaction after it was closed,
|
||||
// so that all future queries will be executed through the driver which created the transaction.
|
||||
func (_m *ChannelMonitor) Unwrap() *ChannelMonitor {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: ChannelMonitor is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *ChannelMonitor) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("ChannelMonitor(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("created_at=")
|
||||
builder.WriteString(_m.CreatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("updated_at=")
|
||||
builder.WriteString(_m.UpdatedAt.Format(time.ANSIC))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("name=")
|
||||
builder.WriteString(_m.Name)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("provider=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Provider))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("endpoint=")
|
||||
builder.WriteString(_m.Endpoint)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("api_key_encrypted=<sensitive>")
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("primary_model=")
|
||||
builder.WriteString(_m.PrimaryModel)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("extra_models=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.ExtraModels))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("group_name=")
|
||||
builder.WriteString(_m.GroupName)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("enabled=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Enabled))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("interval_seconds=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.IntervalSeconds))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.LastCheckedAt; v != nil {
|
||||
builder.WriteString("last_checked_at=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("created_by=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.CreatedBy))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// ChannelMonitors is a parsable slice of ChannelMonitor.
|
||||
type ChannelMonitors []*ChannelMonitor
|
||||
223
backend/ent/channelmonitor/channelmonitor.go
Normal file
223
backend/ent/channelmonitor/channelmonitor.go
Normal file
@@ -0,0 +1,223 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package channelmonitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the channelmonitor type in the database.
|
||||
Label = "channel_monitor"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldCreatedAt holds the string denoting the created_at field in the database.
|
||||
FieldCreatedAt = "created_at"
|
||||
// FieldUpdatedAt holds the string denoting the updated_at field in the database.
|
||||
FieldUpdatedAt = "updated_at"
|
||||
// FieldName holds the string denoting the name field in the database.
|
||||
FieldName = "name"
|
||||
// FieldProvider holds the string denoting the provider field in the database.
|
||||
FieldProvider = "provider"
|
||||
// FieldEndpoint holds the string denoting the endpoint field in the database.
|
||||
FieldEndpoint = "endpoint"
|
||||
// FieldAPIKeyEncrypted holds the string denoting the api_key_encrypted field in the database.
|
||||
FieldAPIKeyEncrypted = "api_key_encrypted"
|
||||
// FieldPrimaryModel holds the string denoting the primary_model field in the database.
|
||||
FieldPrimaryModel = "primary_model"
|
||||
// FieldExtraModels holds the string denoting the extra_models field in the database.
|
||||
FieldExtraModels = "extra_models"
|
||||
// FieldGroupName holds the string denoting the group_name field in the database.
|
||||
FieldGroupName = "group_name"
|
||||
// FieldEnabled holds the string denoting the enabled field in the database.
|
||||
FieldEnabled = "enabled"
|
||||
// FieldIntervalSeconds holds the string denoting the interval_seconds field in the database.
|
||||
FieldIntervalSeconds = "interval_seconds"
|
||||
// FieldLastCheckedAt holds the string denoting the last_checked_at field in the database.
|
||||
FieldLastCheckedAt = "last_checked_at"
|
||||
// FieldCreatedBy holds the string denoting the created_by field in the database.
|
||||
FieldCreatedBy = "created_by"
|
||||
// EdgeHistory holds the string denoting the history edge name in mutations.
|
||||
EdgeHistory = "history"
|
||||
// Table holds the table name of the channelmonitor in the database.
|
||||
Table = "channel_monitors"
|
||||
// HistoryTable is the table that holds the history relation/edge.
|
||||
HistoryTable = "channel_monitor_histories"
|
||||
// HistoryInverseTable is the table name for the ChannelMonitorHistory entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "channelmonitorhistory" package.
|
||||
HistoryInverseTable = "channel_monitor_histories"
|
||||
// HistoryColumn is the table column denoting the history relation/edge.
|
||||
HistoryColumn = "monitor_id"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for channelmonitor fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldName,
|
||||
FieldProvider,
|
||||
FieldEndpoint,
|
||||
FieldAPIKeyEncrypted,
|
||||
FieldPrimaryModel,
|
||||
FieldExtraModels,
|
||||
FieldGroupName,
|
||||
FieldEnabled,
|
||||
FieldIntervalSeconds,
|
||||
FieldLastCheckedAt,
|
||||
FieldCreatedBy,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultCreatedAt holds the default value on creation for the "created_at" field.
|
||||
DefaultCreatedAt func() time.Time
|
||||
// DefaultUpdatedAt holds the default value on creation for the "updated_at" field.
|
||||
DefaultUpdatedAt func() time.Time
|
||||
// UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field.
|
||||
UpdateDefaultUpdatedAt func() time.Time
|
||||
// NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
NameValidator func(string) error
|
||||
// EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
|
||||
EndpointValidator func(string) error
|
||||
// APIKeyEncryptedValidator is a validator for the "api_key_encrypted" field. It is called by the builders before save.
|
||||
APIKeyEncryptedValidator func(string) error
|
||||
// PrimaryModelValidator is a validator for the "primary_model" field. It is called by the builders before save.
|
||||
PrimaryModelValidator func(string) error
|
||||
// DefaultExtraModels holds the default value on creation for the "extra_models" field.
|
||||
DefaultExtraModels []string
|
||||
// DefaultGroupName holds the default value on creation for the "group_name" field.
|
||||
DefaultGroupName string
|
||||
// GroupNameValidator is a validator for the "group_name" field. It is called by the builders before save.
|
||||
GroupNameValidator func(string) error
|
||||
// DefaultEnabled holds the default value on creation for the "enabled" field.
|
||||
DefaultEnabled bool
|
||||
// IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
|
||||
IntervalSecondsValidator func(int) error
|
||||
)
|
||||
|
||||
// Provider defines the type for the "provider" enum field.
|
||||
type Provider string
|
||||
|
||||
// Provider values.
|
||||
const (
|
||||
ProviderOpenai Provider = "openai"
|
||||
ProviderAnthropic Provider = "anthropic"
|
||||
ProviderGemini Provider = "gemini"
|
||||
)
|
||||
|
||||
func (pr Provider) String() string {
|
||||
return string(pr)
|
||||
}
|
||||
|
||||
// ProviderValidator is a validator for the "provider" field enum values. It is called by the builders before save.
|
||||
func ProviderValidator(pr Provider) error {
|
||||
switch pr {
|
||||
case ProviderOpenai, ProviderAnthropic, ProviderGemini:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("channelmonitor: invalid enum value for provider field: %q", pr)
|
||||
}
|
||||
}
|
||||
|
||||
// OrderOption defines the ordering options for the ChannelMonitor queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreatedAt orders the results by the created_at field.
|
||||
func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByUpdatedAt orders the results by the updated_at field.
|
||||
func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByName orders the results by the name field.
|
||||
func ByName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByProvider orders the results by the provider field.
|
||||
func ByProvider(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldProvider, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByEndpoint orders the results by the endpoint field.
|
||||
func ByEndpoint(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldEndpoint, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByAPIKeyEncrypted orders the results by the api_key_encrypted field.
|
||||
func ByAPIKeyEncrypted(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldAPIKeyEncrypted, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByPrimaryModel orders the results by the primary_model field.
|
||||
func ByPrimaryModel(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldPrimaryModel, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByGroupName orders the results by the group_name field.
|
||||
func ByGroupName(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldGroupName, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByEnabled orders the results by the enabled field.
|
||||
func ByEnabled(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldEnabled, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByIntervalSeconds orders the results by the interval_seconds field.
|
||||
func ByIntervalSeconds(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIntervalSeconds, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByLastCheckedAt orders the results by the last_checked_at field.
|
||||
func ByLastCheckedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldLastCheckedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCreatedBy orders the results by the created_by field.
|
||||
func ByCreatedBy(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCreatedBy, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByHistoryCount orders the results by history count.
|
||||
func ByHistoryCount(opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborsCount(s, newHistoryStep(), opts...)
|
||||
}
|
||||
}
|
||||
|
||||
// ByHistory orders the results by history terms.
|
||||
func ByHistory(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newHistoryStep(), append([]sql.OrderTerm{term}, terms...)...)
|
||||
}
|
||||
}
|
||||
func newHistoryStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(HistoryInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, HistoryTable, HistoryColumn),
|
||||
)
|
||||
}
|
||||
724
backend/ent/channelmonitor/where.go
Normal file
724
backend/ent/channelmonitor/where.go
Normal file
@@ -0,0 +1,724 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package channelmonitor
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldID, id))
|
||||
}
|
||||
|
||||
// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ.
|
||||
func CreatedAt(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ.
|
||||
func UpdatedAt(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// Name applies equality check predicate on the "name" field. It's identical to NameEQ.
|
||||
func Name(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// Endpoint applies equality check predicate on the "endpoint" field. It's identical to EndpointEQ.
|
||||
func Endpoint(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// APIKeyEncrypted applies equality check predicate on the "api_key_encrypted" field. It's identical to APIKeyEncryptedEQ.
|
||||
func APIKeyEncrypted(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// PrimaryModel applies equality check predicate on the "primary_model" field. It's identical to PrimaryModelEQ.
|
||||
func PrimaryModel(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// GroupName applies equality check predicate on the "group_name" field. It's identical to GroupNameEQ.
|
||||
func GroupName(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ.
|
||||
func Enabled(v bool) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEnabled, v))
|
||||
}
|
||||
|
||||
// IntervalSeconds applies equality check predicate on the "interval_seconds" field. It's identical to IntervalSecondsEQ.
|
||||
func IntervalSeconds(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// LastCheckedAt applies equality check predicate on the "last_checked_at" field. It's identical to LastCheckedAtEQ.
|
||||
func LastCheckedAt(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// CreatedBy applies equality check predicate on the "created_by" field. It's identical to CreatedByEQ.
|
||||
func CreatedBy(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||
func CreatedAtNEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||
func CreatedAtIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||
func CreatedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||
func CreatedAtGT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||
func CreatedAtGTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||
func CreatedAtLT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||
func CreatedAtLTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||
func UpdatedAtEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||
func UpdatedAtNEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||
func UpdatedAtIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||
func UpdatedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||
func UpdatedAtGT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||
func UpdatedAtGTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||
func UpdatedAtLT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||
func UpdatedAtLTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// NameEQ applies the EQ predicate on the "name" field.
|
||||
func NameEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// NameNEQ applies the NEQ predicate on the "name" field.
|
||||
func NameNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldName, v))
|
||||
}
|
||||
|
||||
// NameIn applies the In predicate on the "name" field.
|
||||
func NameIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldName, vs...))
|
||||
}
|
||||
|
||||
// NameNotIn applies the NotIn predicate on the "name" field.
|
||||
func NameNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldName, vs...))
|
||||
}
|
||||
|
||||
// NameGT applies the GT predicate on the "name" field.
|
||||
func NameGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldName, v))
|
||||
}
|
||||
|
||||
// NameGTE applies the GTE predicate on the "name" field.
|
||||
func NameGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldName, v))
|
||||
}
|
||||
|
||||
// NameLT applies the LT predicate on the "name" field.
|
||||
func NameLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldName, v))
|
||||
}
|
||||
|
||||
// NameLTE applies the LTE predicate on the "name" field.
|
||||
func NameLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldName, v))
|
||||
}
|
||||
|
||||
// NameContains applies the Contains predicate on the "name" field.
|
||||
func NameContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldName, v))
|
||||
}
|
||||
|
||||
// NameHasPrefix applies the HasPrefix predicate on the "name" field.
|
||||
func NameHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldName, v))
|
||||
}
|
||||
|
||||
// NameHasSuffix applies the HasSuffix predicate on the "name" field.
|
||||
func NameHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldName, v))
|
||||
}
|
||||
|
||||
// NameEqualFold applies the EqualFold predicate on the "name" field.
|
||||
func NameEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldName, v))
|
||||
}
|
||||
|
||||
// NameContainsFold applies the ContainsFold predicate on the "name" field.
|
||||
func NameContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldName, v))
|
||||
}
|
||||
|
||||
// ProviderEQ applies the EQ predicate on the "provider" field.
|
||||
func ProviderEQ(v Provider) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldProvider, v))
|
||||
}
|
||||
|
||||
// ProviderNEQ applies the NEQ predicate on the "provider" field.
|
||||
func ProviderNEQ(v Provider) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldProvider, v))
|
||||
}
|
||||
|
||||
// ProviderIn applies the In predicate on the "provider" field.
|
||||
func ProviderIn(vs ...Provider) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldProvider, vs...))
|
||||
}
|
||||
|
||||
// ProviderNotIn applies the NotIn predicate on the "provider" field.
|
||||
func ProviderNotIn(vs ...Provider) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldProvider, vs...))
|
||||
}
|
||||
|
||||
// EndpointEQ applies the EQ predicate on the "endpoint" field.
|
||||
func EndpointEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointNEQ applies the NEQ predicate on the "endpoint" field.
|
||||
func EndpointNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointIn applies the In predicate on the "endpoint" field.
|
||||
func EndpointIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldEndpoint, vs...))
|
||||
}
|
||||
|
||||
// EndpointNotIn applies the NotIn predicate on the "endpoint" field.
|
||||
func EndpointNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldEndpoint, vs...))
|
||||
}
|
||||
|
||||
// EndpointGT applies the GT predicate on the "endpoint" field.
|
||||
func EndpointGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointGTE applies the GTE predicate on the "endpoint" field.
|
||||
func EndpointGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointLT applies the LT predicate on the "endpoint" field.
|
||||
func EndpointLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointLTE applies the LTE predicate on the "endpoint" field.
|
||||
func EndpointLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointContains applies the Contains predicate on the "endpoint" field.
|
||||
func EndpointContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointHasPrefix applies the HasPrefix predicate on the "endpoint" field.
|
||||
func EndpointHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointHasSuffix applies the HasSuffix predicate on the "endpoint" field.
|
||||
func EndpointHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointEqualFold applies the EqualFold predicate on the "endpoint" field.
|
||||
func EndpointEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// EndpointContainsFold applies the ContainsFold predicate on the "endpoint" field.
|
||||
func EndpointContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldEndpoint, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedEQ applies the EQ predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedNEQ applies the NEQ predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedIn applies the In predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldAPIKeyEncrypted, vs...))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedNotIn applies the NotIn predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldAPIKeyEncrypted, vs...))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedGT applies the GT predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedGTE applies the GTE predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedLT applies the LT predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedLTE applies the LTE predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedContains applies the Contains predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedHasPrefix applies the HasPrefix predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedHasSuffix applies the HasSuffix predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedEqualFold applies the EqualFold predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// APIKeyEncryptedContainsFold applies the ContainsFold predicate on the "api_key_encrypted" field.
|
||||
func APIKeyEncryptedContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldAPIKeyEncrypted, v))
|
||||
}
|
||||
|
||||
// PrimaryModelEQ applies the EQ predicate on the "primary_model" field.
|
||||
func PrimaryModelEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelNEQ applies the NEQ predicate on the "primary_model" field.
|
||||
func PrimaryModelNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelIn applies the In predicate on the "primary_model" field.
|
||||
func PrimaryModelIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldPrimaryModel, vs...))
|
||||
}
|
||||
|
||||
// PrimaryModelNotIn applies the NotIn predicate on the "primary_model" field.
|
||||
func PrimaryModelNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldPrimaryModel, vs...))
|
||||
}
|
||||
|
||||
// PrimaryModelGT applies the GT predicate on the "primary_model" field.
|
||||
func PrimaryModelGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelGTE applies the GTE predicate on the "primary_model" field.
|
||||
func PrimaryModelGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelLT applies the LT predicate on the "primary_model" field.
|
||||
func PrimaryModelLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelLTE applies the LTE predicate on the "primary_model" field.
|
||||
func PrimaryModelLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelContains applies the Contains predicate on the "primary_model" field.
|
||||
func PrimaryModelContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelHasPrefix applies the HasPrefix predicate on the "primary_model" field.
|
||||
func PrimaryModelHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelHasSuffix applies the HasSuffix predicate on the "primary_model" field.
|
||||
func PrimaryModelHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelEqualFold applies the EqualFold predicate on the "primary_model" field.
|
||||
func PrimaryModelEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// PrimaryModelContainsFold applies the ContainsFold predicate on the "primary_model" field.
|
||||
func PrimaryModelContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldPrimaryModel, v))
|
||||
}
|
||||
|
||||
// GroupNameEQ applies the EQ predicate on the "group_name" field.
|
||||
func GroupNameEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameNEQ applies the NEQ predicate on the "group_name" field.
|
||||
func GroupNameNEQ(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameIn applies the In predicate on the "group_name" field.
|
||||
func GroupNameIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldGroupName, vs...))
|
||||
}
|
||||
|
||||
// GroupNameNotIn applies the NotIn predicate on the "group_name" field.
|
||||
func GroupNameNotIn(vs ...string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldGroupName, vs...))
|
||||
}
|
||||
|
||||
// GroupNameGT applies the GT predicate on the "group_name" field.
|
||||
func GroupNameGT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameGTE applies the GTE predicate on the "group_name" field.
|
||||
func GroupNameGTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameLT applies the LT predicate on the "group_name" field.
|
||||
func GroupNameLT(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameLTE applies the LTE predicate on the "group_name" field.
|
||||
func GroupNameLTE(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameContains applies the Contains predicate on the "group_name" field.
|
||||
func GroupNameContains(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContains(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameHasPrefix applies the HasPrefix predicate on the "group_name" field.
|
||||
func GroupNameHasPrefix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasPrefix(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameHasSuffix applies the HasSuffix predicate on the "group_name" field.
|
||||
func GroupNameHasSuffix(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldHasSuffix(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameIsNil applies the IsNil predicate on the "group_name" field.
|
||||
func GroupNameIsNil() predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIsNull(FieldGroupName))
|
||||
}
|
||||
|
||||
// GroupNameNotNil applies the NotNil predicate on the "group_name" field.
|
||||
func GroupNameNotNil() predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotNull(FieldGroupName))
|
||||
}
|
||||
|
||||
// GroupNameEqualFold applies the EqualFold predicate on the "group_name" field.
|
||||
func GroupNameEqualFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEqualFold(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// GroupNameContainsFold applies the ContainsFold predicate on the "group_name" field.
|
||||
func GroupNameContainsFold(v string) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldContainsFold(FieldGroupName, v))
|
||||
}
|
||||
|
||||
// EnabledEQ applies the EQ predicate on the "enabled" field.
|
||||
func EnabledEQ(v bool) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldEnabled, v))
|
||||
}
|
||||
|
||||
// EnabledNEQ applies the NEQ predicate on the "enabled" field.
|
||||
func EnabledNEQ(v bool) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldEnabled, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsEQ applies the EQ predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsEQ(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsNEQ applies the NEQ predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsNEQ(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsIn applies the In predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsIn(vs ...int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldIntervalSeconds, vs...))
|
||||
}
|
||||
|
||||
// IntervalSecondsNotIn applies the NotIn predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsNotIn(vs ...int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldIntervalSeconds, vs...))
|
||||
}
|
||||
|
||||
// IntervalSecondsGT applies the GT predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsGT(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsGTE applies the GTE predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsGTE(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsLT applies the LT predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsLT(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// IntervalSecondsLTE applies the LTE predicate on the "interval_seconds" field.
|
||||
func IntervalSecondsLTE(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtEQ applies the EQ predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtNEQ applies the NEQ predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtNEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtIn applies the In predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldLastCheckedAt, vs...))
|
||||
}
|
||||
|
||||
// LastCheckedAtNotIn applies the NotIn predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtNotIn(vs ...time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldLastCheckedAt, vs...))
|
||||
}
|
||||
|
||||
// LastCheckedAtGT applies the GT predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtGT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtGTE applies the GTE predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtGTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtLT applies the LT predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtLT(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtLTE applies the LTE predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtLTE(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldLastCheckedAt, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtIsNil applies the IsNil predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtIsNil() predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIsNull(FieldLastCheckedAt))
|
||||
}
|
||||
|
||||
// LastCheckedAtNotNil applies the NotNil predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtNotNil() predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotNull(FieldLastCheckedAt))
|
||||
}
|
||||
|
||||
// CreatedByEQ applies the EQ predicate on the "created_by" field.
|
||||
func CreatedByEQ(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedByNEQ applies the NEQ predicate on the "created_by" field.
|
||||
func CreatedByNEQ(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedByIn applies the In predicate on the "created_by" field.
|
||||
func CreatedByIn(vs ...int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldCreatedBy, vs...))
|
||||
}
|
||||
|
||||
// CreatedByNotIn applies the NotIn predicate on the "created_by" field.
|
||||
func CreatedByNotIn(vs ...int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldCreatedBy, vs...))
|
||||
}
|
||||
|
||||
// CreatedByGT applies the GT predicate on the "created_by" field.
|
||||
func CreatedByGT(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedByGTE applies the GTE predicate on the "created_by" field.
|
||||
func CreatedByGTE(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedByLT applies the LT predicate on the "created_by" field.
|
||||
func CreatedByLT(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// CreatedByLTE applies the LTE predicate on the "created_by" field.
|
||||
func CreatedByLTE(v int64) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldCreatedBy, v))
|
||||
}
|
||||
|
||||
// HasHistory applies the HasEdge predicate on the "history" edge.
|
||||
func HasHistory() predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, HistoryTable, HistoryColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasHistoryWith applies the HasEdge predicate on the "history" edge with a given conditions (other predicates).
|
||||
func HasHistoryWith(preds ...predicate.ChannelMonitorHistory) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(func(s *sql.Selector) {
|
||||
step := newHistoryStep()
|
||||
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.ChannelMonitor) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.ChannelMonitor) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.ChannelMonitor) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.NotPredicates(p))
|
||||
}
|
||||
1270
backend/ent/channelmonitor_create.go
Normal file
1270
backend/ent/channelmonitor_create.go
Normal file
File diff suppressed because it is too large
Load Diff
88
backend/ent/channelmonitor_delete.go
Normal file
88
backend/ent/channelmonitor_delete.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorDelete is the builder for deleting a ChannelMonitor entity.
|
||||
type ChannelMonitorDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorDelete builder.
|
||||
func (_d *ChannelMonitorDelete) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorDelete {
|
||||
_d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (_d *ChannelMonitorDelete) Exec(ctx context.Context) (int, error) {
|
||||
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *ChannelMonitorDelete) ExecX(ctx context.Context) int {
|
||||
n, err := _d.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (_d *ChannelMonitorDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := sqlgraph.NewDeleteSpec(channelmonitor.Table, sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64))
|
||||
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
_d.mutation.done = true
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// ChannelMonitorDeleteOne is the builder for deleting a single ChannelMonitor entity.
|
||||
type ChannelMonitorDeleteOne struct {
|
||||
_d *ChannelMonitorDelete
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorDelete builder.
|
||||
func (_d *ChannelMonitorDeleteOne) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorDeleteOne {
|
||||
_d._d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (_d *ChannelMonitorDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := _d._d.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{channelmonitor.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *ChannelMonitorDeleteOne) ExecX(ctx context.Context) {
|
||||
if err := _d.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
643
backend/ent/channelmonitor_query.go
Normal file
643
backend/ent/channelmonitor_query.go
Normal file
@@ -0,0 +1,643 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorQuery is the builder for querying ChannelMonitor entities.
|
||||
type ChannelMonitorQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []channelmonitor.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.ChannelMonitor
|
||||
withHistory *ChannelMonitorHistoryQuery
|
||||
modifiers []func(*sql.Selector)
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Where adds a new predicate for the ChannelMonitorQuery builder.
|
||||
func (_q *ChannelMonitorQuery) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorQuery {
|
||||
_q.predicates = append(_q.predicates, ps...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// Limit the number of records to be returned by this query.
|
||||
func (_q *ChannelMonitorQuery) Limit(limit int) *ChannelMonitorQuery {
|
||||
_q.ctx.Limit = &limit
|
||||
return _q
|
||||
}
|
||||
|
||||
// Offset to start from.
|
||||
func (_q *ChannelMonitorQuery) Offset(offset int) *ChannelMonitorQuery {
|
||||
_q.ctx.Offset = &offset
|
||||
return _q
|
||||
}
|
||||
|
||||
// Unique configures the query builder to filter duplicate records on query.
|
||||
// By default, unique is set to true, and can be disabled using this method.
|
||||
func (_q *ChannelMonitorQuery) Unique(unique bool) *ChannelMonitorQuery {
|
||||
_q.ctx.Unique = &unique
|
||||
return _q
|
||||
}
|
||||
|
||||
// Order specifies how the records should be ordered.
|
||||
func (_q *ChannelMonitorQuery) Order(o ...channelmonitor.OrderOption) *ChannelMonitorQuery {
|
||||
_q.order = append(_q.order, o...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// QueryHistory chains the current query on the "history" edge.
|
||||
func (_q *ChannelMonitorQuery) QueryHistory() *ChannelMonitorHistoryQuery {
|
||||
query := (&ChannelMonitorHistoryClient{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(channelmonitor.Table, channelmonitor.FieldID, selector),
|
||||
sqlgraph.To(channelmonitorhistory.Table, channelmonitorhistory.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, channelmonitor.HistoryTable, channelmonitor.HistoryColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first ChannelMonitor entity from the query.
|
||||
// Returns a *NotFoundError when no ChannelMonitor was found.
|
||||
func (_q *ChannelMonitorQuery) First(ctx context.Context) (*ChannelMonitor, error) {
|
||||
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nil, &NotFoundError{channelmonitor.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) FirstX(ctx context.Context) *ChannelMonitor {
|
||||
node, err := _q.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first ChannelMonitor ID from the query.
|
||||
// Returns a *NotFoundError when no ChannelMonitor ID was found.
|
||||
func (_q *ChannelMonitorQuery) FirstID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
err = &NotFoundError{channelmonitor.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) FirstIDX(ctx context.Context) int64 {
|
||||
id, err := _q.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single ChannelMonitor entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one ChannelMonitor entity is found.
|
||||
// Returns a *NotFoundError when no ChannelMonitor entities are found.
|
||||
func (_q *ChannelMonitorQuery) Only(ctx context.Context) (*ChannelMonitor, error) {
|
||||
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(nodes) {
|
||||
case 1:
|
||||
return nodes[0], nil
|
||||
case 0:
|
||||
return nil, &NotFoundError{channelmonitor.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{channelmonitor.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) OnlyX(ctx context.Context) *ChannelMonitor {
|
||||
node, err := _q.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only ChannelMonitor ID in the query.
|
||||
// Returns a *NotSingularError when more than one ChannelMonitor ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (_q *ChannelMonitorQuery) OnlyID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
|
||||
return
|
||||
}
|
||||
switch len(ids) {
|
||||
case 1:
|
||||
id = ids[0]
|
||||
case 0:
|
||||
err = &NotFoundError{channelmonitor.Label}
|
||||
default:
|
||||
err = &NotSingularError{channelmonitor.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) OnlyIDX(ctx context.Context) int64 {
|
||||
id, err := _q.OnlyID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// All executes the query and returns a list of ChannelMonitors.
|
||||
func (_q *ChannelMonitorQuery) All(ctx context.Context) ([]*ChannelMonitor, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr := querierAll[[]*ChannelMonitor, *ChannelMonitorQuery]()
|
||||
return withInterceptors[[]*ChannelMonitor](ctx, _q, qr, _q.inters)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) AllX(ctx context.Context) []*ChannelMonitor {
|
||||
nodes, err := _q.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of ChannelMonitor IDs.
|
||||
func (_q *ChannelMonitorQuery) IDs(ctx context.Context) (ids []int64, err error) {
|
||||
if _q.ctx.Unique == nil && _q.path != nil {
|
||||
_q.Unique(true)
|
||||
}
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
|
||||
if err = _q.Select(channelmonitor.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) IDsX(ctx context.Context) []int64 {
|
||||
ids, err := _q.IDs(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// Count returns the count of the given query.
|
||||
func (_q *ChannelMonitorQuery) Count(ctx context.Context) (int, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return withInterceptors[int](ctx, _q, querierCount[*ChannelMonitorQuery](), _q.inters)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) CountX(ctx context.Context) int {
|
||||
count, err := _q.Count(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Exist returns true if the query has elements in the graph.
|
||||
func (_q *ChannelMonitorQuery) Exist(ctx context.Context) (bool, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||
switch _, err := _q.FirstID(ctx); {
|
||||
case IsNotFound(err):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExistX is like Exist, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := _q.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the ChannelMonitorQuery builder, including all associated steps. It can be
|
||||
// used to prepare common query builders and use them differently after the clone is made.
|
||||
func (_q *ChannelMonitorQuery) Clone() *ChannelMonitorQuery {
|
||||
if _q == nil {
|
||||
return nil
|
||||
}
|
||||
return &ChannelMonitorQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]channelmonitor.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.ChannelMonitor{}, _q.predicates...),
|
||||
withHistory: _q.withHistory.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
}
|
||||
}
|
||||
|
||||
// WithHistory tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "history" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *ChannelMonitorQuery) WithHistory(opts ...func(*ChannelMonitorHistoryQuery)) *ChannelMonitorQuery {
|
||||
query := (&ChannelMonitorHistoryClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withHistory = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// Count int `json:"count,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ChannelMonitor.Query().
|
||||
// GroupBy(channelmonitor.FieldCreatedAt).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ChannelMonitorQuery) GroupBy(field string, fields ...string) *ChannelMonitorGroupBy {
|
||||
_q.ctx.Fields = append([]string{field}, fields...)
|
||||
grbuild := &ChannelMonitorGroupBy{build: _q}
|
||||
grbuild.flds = &_q.ctx.Fields
|
||||
grbuild.label = channelmonitor.Label
|
||||
grbuild.scan = grbuild.Scan
|
||||
return grbuild
|
||||
}
|
||||
|
||||
// Select allows the selection one or more fields/columns for the given query,
|
||||
// instead of selecting all fields in the entity.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ChannelMonitor.Query().
|
||||
// Select(channelmonitor.FieldCreatedAt).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ChannelMonitorQuery) Select(fields ...string) *ChannelMonitorSelect {
|
||||
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||
sbuild := &ChannelMonitorSelect{ChannelMonitorQuery: _q}
|
||||
sbuild.label = channelmonitor.Label
|
||||
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||
return sbuild
|
||||
}
|
||||
|
||||
// Aggregate returns a ChannelMonitorSelect configured with the given aggregations.
|
||||
func (_q *ChannelMonitorQuery) Aggregate(fns ...AggregateFunc) *ChannelMonitorSelect {
|
||||
return _q.Select().Aggregate(fns...)
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) prepareQuery(ctx context.Context) error {
|
||||
for _, inter := range _q.inters {
|
||||
if inter == nil {
|
||||
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||
}
|
||||
if trv, ok := inter.(Traverser); ok {
|
||||
if err := trv.Traverse(ctx, _q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range _q.ctx.Fields {
|
||||
if !channelmonitor.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
}
|
||||
if _q.path != nil {
|
||||
prev, err := _q.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_q.sql = prev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ChannelMonitor, error) {
|
||||
var (
|
||||
nodes = []*ChannelMonitor{}
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [1]bool{
|
||||
_q.withHistory != nil,
|
||||
}
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*ChannelMonitor).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &ChannelMonitor{config: _q.config}
|
||||
nodes = append(nodes, node)
|
||||
node.Edges.loadedTypes = loadedTypes
|
||||
return node.assignValues(columns, values)
|
||||
}
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
for i := range hooks {
|
||||
hooks[i](ctx, _spec)
|
||||
}
|
||||
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
if query := _q.withHistory; query != nil {
|
||||
if err := _q.loadHistory(ctx, query, nodes,
|
||||
func(n *ChannelMonitor) { n.Edges.History = []*ChannelMonitorHistory{} },
|
||||
func(n *ChannelMonitor, e *ChannelMonitorHistory) { n.Edges.History = append(n.Edges.History, e) }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) loadHistory(ctx context.Context, query *ChannelMonitorHistoryQuery, nodes []*ChannelMonitor, init func(*ChannelMonitor), assign func(*ChannelMonitor, *ChannelMonitorHistory)) error {
|
||||
fks := make([]driver.Value, 0, len(nodes))
|
||||
nodeids := make(map[int64]*ChannelMonitor)
|
||||
for i := range nodes {
|
||||
fks = append(fks, nodes[i].ID)
|
||||
nodeids[nodes[i].ID] = nodes[i]
|
||||
if init != nil {
|
||||
init(nodes[i])
|
||||
}
|
||||
}
|
||||
if len(query.ctx.Fields) > 0 {
|
||||
query.ctx.AppendFieldOnce(channelmonitorhistory.FieldMonitorID)
|
||||
}
|
||||
query.Where(predicate.ChannelMonitorHistory(func(s *sql.Selector) {
|
||||
s.Where(sql.InValues(s.C(channelmonitor.HistoryColumn), fks...))
|
||||
}))
|
||||
neighbors, err := query.All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, n := range neighbors {
|
||||
fk := n.MonitorID
|
||||
node, ok := nodeids[fk]
|
||||
if !ok {
|
||||
return fmt.Errorf(`unexpected referenced foreign-key "monitor_id" returned %v for node %v`, fk, n.ID)
|
||||
}
|
||||
assign(node, n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
_spec.Node.Columns = _q.ctx.Fields
|
||||
if len(_q.ctx.Fields) > 0 {
|
||||
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||
}
|
||||
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := sqlgraph.NewQuerySpec(channelmonitor.Table, channelmonitor.Columns, sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64))
|
||||
_spec.From = _q.sql
|
||||
if unique := _q.ctx.Unique; unique != nil {
|
||||
_spec.Unique = *unique
|
||||
} else if _q.path != nil {
|
||||
_spec.Unique = true
|
||||
}
|
||||
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, channelmonitor.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != channelmonitor.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _q.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
_spec.Limit = *limit
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
_spec.Offset = *offset
|
||||
}
|
||||
if ps := _q.order; len(ps) > 0 {
|
||||
_spec.Order = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
return _spec
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(_q.driver.Dialect())
|
||||
t1 := builder.Table(channelmonitor.Table)
|
||||
columns := _q.ctx.Fields
|
||||
if len(columns) == 0 {
|
||||
columns = channelmonitor.Columns
|
||||
}
|
||||
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||
if _q.sql != nil {
|
||||
selector = _q.sql
|
||||
selector.Select(selector.Columns(columns...)...)
|
||||
}
|
||||
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||
selector.Distinct()
|
||||
}
|
||||
for _, m := range _q.modifiers {
|
||||
m(selector)
|
||||
}
|
||||
for _, p := range _q.predicates {
|
||||
p(selector)
|
||||
}
|
||||
for _, p := range _q.order {
|
||||
p(selector)
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
// limit is mandatory for offset clause. We start
|
||||
// with default value, and override it below if needed.
|
||||
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
selector.Limit(*limit)
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
|
||||
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
|
||||
// either committed or rolled-back.
|
||||
func (_q *ChannelMonitorQuery) ForUpdate(opts ...sql.LockOption) *ChannelMonitorQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForUpdate(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
|
||||
// on any rows that are read. Other sessions can read the rows, but cannot modify them
|
||||
// until your transaction commits.
|
||||
func (_q *ChannelMonitorQuery) ForShare(opts ...sql.LockOption) *ChannelMonitorQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForShare(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// ChannelMonitorGroupBy is the group-by builder for ChannelMonitor entities.
|
||||
type ChannelMonitorGroupBy struct {
|
||||
selector
|
||||
build *ChannelMonitorQuery
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (_g *ChannelMonitorGroupBy) Aggregate(fns ...AggregateFunc) *ChannelMonitorGroupBy {
|
||||
_g.fns = append(_g.fns, fns...)
|
||||
return _g
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_g *ChannelMonitorGroupBy) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
|
||||
if err := _g.build.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*ChannelMonitorQuery, *ChannelMonitorGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||
}
|
||||
|
||||
func (_g *ChannelMonitorGroupBy) sqlScan(ctx context.Context, root *ChannelMonitorQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx).Select()
|
||||
aggregation := make([]string, 0, len(_g.fns))
|
||||
for _, fn := range _g.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
if len(selector.SelectedColumns()) == 0 {
|
||||
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||
for _, f := range *_g.flds {
|
||||
columns = append(columns, selector.C(f))
|
||||
}
|
||||
columns = append(columns, aggregation...)
|
||||
selector.Select(columns...)
|
||||
}
|
||||
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||
if err := selector.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
|
||||
// ChannelMonitorSelect is the builder for selecting fields of ChannelMonitor entities.
|
||||
type ChannelMonitorSelect struct {
|
||||
*ChannelMonitorQuery
|
||||
selector
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the selector query.
|
||||
func (_s *ChannelMonitorSelect) Aggregate(fns ...AggregateFunc) *ChannelMonitorSelect {
|
||||
_s.fns = append(_s.fns, fns...)
|
||||
return _s
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_s *ChannelMonitorSelect) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
|
||||
if err := _s.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*ChannelMonitorQuery, *ChannelMonitorSelect](ctx, _s.ChannelMonitorQuery, _s, _s.inters, v)
|
||||
}
|
||||
|
||||
func (_s *ChannelMonitorSelect) sqlScan(ctx context.Context, root *ChannelMonitorQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx)
|
||||
aggregation := make([]string, 0, len(_s.fns))
|
||||
for _, fn := range _s.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
switch n := len(*_s.selector.flds); {
|
||||
case n == 0 && len(aggregation) > 0:
|
||||
selector.Select(aggregation...)
|
||||
case n != 0 && len(aggregation) > 0:
|
||||
selector.AppendSelect(aggregation...)
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
918
backend/ent/channelmonitor_update.go
Normal file
918
backend/ent/channelmonitor_update.go
Normal file
@@ -0,0 +1,918 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/dialect/sql/sqljson"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorUpdate is the builder for updating ChannelMonitor entities.
|
||||
type ChannelMonitorUpdate struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorUpdate builder.
|
||||
func (_u *ChannelMonitorUpdate) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorUpdate {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *ChannelMonitorUpdate) SetUpdatedAt(v time.Time) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetName sets the "name" field.
|
||||
func (_u *ChannelMonitorUpdate) SetName(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetName(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableName sets the "name" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableName(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetName(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetProvider sets the "provider" field.
|
||||
func (_u *ChannelMonitorUpdate) SetProvider(v channelmonitor.Provider) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetProvider(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableProvider sets the "provider" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableProvider(v *channelmonitor.Provider) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetProvider(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (_u *ChannelMonitorUpdate) SetEndpoint(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetEndpoint(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableEndpoint sets the "endpoint" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableEndpoint(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetEndpoint(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIKeyEncrypted sets the "api_key_encrypted" field.
|
||||
func (_u *ChannelMonitorUpdate) SetAPIKeyEncrypted(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetAPIKeyEncrypted(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIKeyEncrypted sets the "api_key_encrypted" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableAPIKeyEncrypted(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetAPIKeyEncrypted(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPrimaryModel sets the "primary_model" field.
|
||||
func (_u *ChannelMonitorUpdate) SetPrimaryModel(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetPrimaryModel(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillablePrimaryModel sets the "primary_model" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillablePrimaryModel(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetPrimaryModel(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExtraModels sets the "extra_models" field.
|
||||
func (_u *ChannelMonitorUpdate) SetExtraModels(v []string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetExtraModels(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AppendExtraModels appends value to the "extra_models" field.
|
||||
func (_u *ChannelMonitorUpdate) AppendExtraModels(v []string) *ChannelMonitorUpdate {
|
||||
_u.mutation.AppendExtraModels(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetGroupName sets the "group_name" field.
|
||||
func (_u *ChannelMonitorUpdate) SetGroupName(v string) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetGroupName(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableGroupName sets the "group_name" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableGroupName(v *string) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetGroupName(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearGroupName clears the value of the "group_name" field.
|
||||
func (_u *ChannelMonitorUpdate) ClearGroupName() *ChannelMonitorUpdate {
|
||||
_u.mutation.ClearGroupName()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEnabled sets the "enabled" field.
|
||||
func (_u *ChannelMonitorUpdate) SetEnabled(v bool) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetEnabled(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableEnabled(v *bool) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetEnabled(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetIntervalSeconds sets the "interval_seconds" field.
|
||||
func (_u *ChannelMonitorUpdate) SetIntervalSeconds(v int) *ChannelMonitorUpdate {
|
||||
_u.mutation.ResetIntervalSeconds()
|
||||
_u.mutation.SetIntervalSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableIntervalSeconds sets the "interval_seconds" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableIntervalSeconds(v *int) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetIntervalSeconds(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddIntervalSeconds adds value to the "interval_seconds" field.
|
||||
func (_u *ChannelMonitorUpdate) AddIntervalSeconds(v int) *ChannelMonitorUpdate {
|
||||
_u.mutation.AddIntervalSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdate) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetLastCheckedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLastCheckedAt sets the "last_checked_at" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableLastCheckedAt(v *time.Time) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetLastCheckedAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLastCheckedAt clears the value of the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdate) ClearLastCheckedAt() *ChannelMonitorUpdate {
|
||||
_u.mutation.ClearLastCheckedAt()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCreatedBy sets the "created_by" field.
|
||||
func (_u *ChannelMonitorUpdate) SetCreatedBy(v int64) *ChannelMonitorUpdate {
|
||||
_u.mutation.ResetCreatedBy()
|
||||
_u.mutation.SetCreatedBy(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableCreatedBy(v *int64) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetCreatedBy(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddCreatedBy adds value to the "created_by" field.
|
||||
func (_u *ChannelMonitorUpdate) AddCreatedBy(v int64) *ChannelMonitorUpdate {
|
||||
_u.mutation.AddCreatedBy(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddHistoryIDs adds the "history" edge to the ChannelMonitorHistory entity by IDs.
|
||||
func (_u *ChannelMonitorUpdate) AddHistoryIDs(ids ...int64) *ChannelMonitorUpdate {
|
||||
_u.mutation.AddHistoryIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddHistory adds the "history" edges to the ChannelMonitorHistory entity.
|
||||
func (_u *ChannelMonitorUpdate) AddHistory(v ...*ChannelMonitorHistory) *ChannelMonitorUpdate {
|
||||
ids := make([]int64, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddHistoryIDs(ids...)
|
||||
}
|
||||
|
||||
// Mutation returns the ChannelMonitorMutation object of the builder.
|
||||
func (_u *ChannelMonitorUpdate) Mutation() *ChannelMonitorMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearHistory clears all "history" edges to the ChannelMonitorHistory entity.
|
||||
func (_u *ChannelMonitorUpdate) ClearHistory() *ChannelMonitorUpdate {
|
||||
_u.mutation.ClearHistory()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveHistoryIDs removes the "history" edge to ChannelMonitorHistory entities by IDs.
|
||||
func (_u *ChannelMonitorUpdate) RemoveHistoryIDs(ids ...int64) *ChannelMonitorUpdate {
|
||||
_u.mutation.RemoveHistoryIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveHistory removes "history" edges to ChannelMonitorHistory entities.
|
||||
func (_u *ChannelMonitorUpdate) RemoveHistory(v ...*ChannelMonitorHistory) *ChannelMonitorUpdate {
|
||||
ids := make([]int64, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveHistoryIDs(ids...)
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *ChannelMonitorUpdate) Save(ctx context.Context) (int, error) {
|
||||
_u.defaults()
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorUpdate) SaveX(ctx context.Context) int {
|
||||
affected, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_u *ChannelMonitorUpdate) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorUpdate) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (_u *ChannelMonitorUpdate) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := channelmonitor.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *ChannelMonitorUpdate) check() error {
|
||||
if v, ok := _u.mutation.Name(); ok {
|
||||
if err := channelmonitor.NameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Provider(); ok {
|
||||
if err := channelmonitor.ProviderValidator(v); err != nil {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Endpoint(); ok {
|
||||
if err := channelmonitor.EndpointValidator(v); err != nil {
|
||||
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.endpoint": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIKeyEncrypted(); ok {
|
||||
if err := channelmonitor.APIKeyEncryptedValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_key_encrypted", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.api_key_encrypted": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.PrimaryModel(); ok {
|
||||
if err := channelmonitor.PrimaryModelValidator(v); err != nil {
|
||||
return &ValidationError{Name: "primary_model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.primary_model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.GroupName(); ok {
|
||||
if err := channelmonitor.GroupNameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "group_name", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.group_name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.IntervalSeconds(); ok {
|
||||
if err := channelmonitor.IntervalSecondsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *ChannelMonitorUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(channelmonitor.Table, channelmonitor.Columns, sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64))
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Name(); ok {
|
||||
_spec.SetField(channelmonitor.FieldName, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitor.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Endpoint(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEndpoint, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIKeyEncrypted(); ok {
|
||||
_spec.SetField(channelmonitor.FieldAPIKeyEncrypted, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.PrimaryModel(); ok {
|
||||
_spec.SetField(channelmonitor.FieldPrimaryModel, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExtraModels(); ok {
|
||||
_spec.SetField(channelmonitor.FieldExtraModels, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AppendedExtraModels(); ok {
|
||||
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||
sqljson.Append(u, channelmonitor.FieldExtraModels, value)
|
||||
})
|
||||
}
|
||||
if value, ok := _u.mutation.GroupName(); ok {
|
||||
_spec.SetField(channelmonitor.FieldGroupName, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.GroupNameCleared() {
|
||||
_spec.ClearField(channelmonitor.FieldGroupName, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.Enabled(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEnabled, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.IntervalSeconds(); ok {
|
||||
_spec.SetField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LastCheckedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.LastCheckedAtCleared() {
|
||||
_spec.ClearField(channelmonitor.FieldLastCheckedAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||
_spec.SetField(channelmonitor.FieldCreatedBy, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||
_spec.AddField(channelmonitor.FieldCreatedBy, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.HistoryCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedHistoryIDs(); len(nodes) > 0 && !_u.mutation.HistoryCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.HistoryIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.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{channelmonitor.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
// ChannelMonitorUpdateOne is the builder for updating a single ChannelMonitor entity.
|
||||
type ChannelMonitorUpdateOne struct {
|
||||
config
|
||||
fields []string
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorMutation
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetUpdatedAt(v time.Time) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetName sets the "name" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetName(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetName(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableName sets the "name" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableName(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetName(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetProvider sets the "provider" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetProvider(v channelmonitor.Provider) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetProvider(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableProvider sets the "provider" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableProvider(v *channelmonitor.Provider) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetProvider(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEndpoint sets the "endpoint" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetEndpoint(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetEndpoint(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableEndpoint sets the "endpoint" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableEndpoint(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetEndpoint(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetAPIKeyEncrypted sets the "api_key_encrypted" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetAPIKeyEncrypted(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetAPIKeyEncrypted(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableAPIKeyEncrypted sets the "api_key_encrypted" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableAPIKeyEncrypted(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetAPIKeyEncrypted(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPrimaryModel sets the "primary_model" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetPrimaryModel(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetPrimaryModel(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillablePrimaryModel sets the "primary_model" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillablePrimaryModel(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetPrimaryModel(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetExtraModels sets the "extra_models" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetExtraModels(v []string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetExtraModels(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AppendExtraModels appends value to the "extra_models" field.
|
||||
func (_u *ChannelMonitorUpdateOne) AppendExtraModels(v []string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.AppendExtraModels(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetGroupName sets the "group_name" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetGroupName(v string) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetGroupName(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableGroupName sets the "group_name" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableGroupName(v *string) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetGroupName(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearGroupName clears the value of the "group_name" field.
|
||||
func (_u *ChannelMonitorUpdateOne) ClearGroupName() *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ClearGroupName()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetEnabled sets the "enabled" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetEnabled(v bool) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetEnabled(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableEnabled(v *bool) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetEnabled(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetIntervalSeconds sets the "interval_seconds" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetIntervalSeconds(v int) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ResetIntervalSeconds()
|
||||
_u.mutation.SetIntervalSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableIntervalSeconds sets the "interval_seconds" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableIntervalSeconds(v *int) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetIntervalSeconds(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddIntervalSeconds adds value to the "interval_seconds" field.
|
||||
func (_u *ChannelMonitorUpdateOne) AddIntervalSeconds(v int) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.AddIntervalSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetLastCheckedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLastCheckedAt sets the "last_checked_at" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableLastCheckedAt(v *time.Time) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetLastCheckedAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLastCheckedAt clears the value of the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdateOne) ClearLastCheckedAt() *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ClearLastCheckedAt()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCreatedBy sets the "created_by" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetCreatedBy(v int64) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ResetCreatedBy()
|
||||
_u.mutation.SetCreatedBy(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCreatedBy sets the "created_by" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableCreatedBy(v *int64) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetCreatedBy(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddCreatedBy adds value to the "created_by" field.
|
||||
func (_u *ChannelMonitorUpdateOne) AddCreatedBy(v int64) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.AddCreatedBy(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddHistoryIDs adds the "history" edge to the ChannelMonitorHistory entity by IDs.
|
||||
func (_u *ChannelMonitorUpdateOne) AddHistoryIDs(ids ...int64) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.AddHistoryIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddHistory adds the "history" edges to the ChannelMonitorHistory entity.
|
||||
func (_u *ChannelMonitorUpdateOne) AddHistory(v ...*ChannelMonitorHistory) *ChannelMonitorUpdateOne {
|
||||
ids := make([]int64, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.AddHistoryIDs(ids...)
|
||||
}
|
||||
|
||||
// Mutation returns the ChannelMonitorMutation object of the builder.
|
||||
func (_u *ChannelMonitorUpdateOne) Mutation() *ChannelMonitorMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearHistory clears all "history" edges to the ChannelMonitorHistory entity.
|
||||
func (_u *ChannelMonitorUpdateOne) ClearHistory() *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ClearHistory()
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveHistoryIDs removes the "history" edge to ChannelMonitorHistory entities by IDs.
|
||||
func (_u *ChannelMonitorUpdateOne) RemoveHistoryIDs(ids ...int64) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.RemoveHistoryIDs(ids...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// RemoveHistory removes "history" edges to ChannelMonitorHistory entities.
|
||||
func (_u *ChannelMonitorUpdateOne) RemoveHistory(v ...*ChannelMonitorHistory) *ChannelMonitorUpdateOne {
|
||||
ids := make([]int64, len(v))
|
||||
for i := range v {
|
||||
ids[i] = v[i].ID
|
||||
}
|
||||
return _u.RemoveHistoryIDs(ids...)
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorUpdate builder.
|
||||
func (_u *ChannelMonitorUpdateOne) Where(ps ...predicate.ChannelMonitor) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||
// The default is selecting all fields defined in the entity schema.
|
||||
func (_u *ChannelMonitorUpdateOne) Select(field string, fields ...string) *ChannelMonitorUpdateOne {
|
||||
_u.fields = append([]string{field}, fields...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the updated ChannelMonitor entity.
|
||||
func (_u *ChannelMonitorUpdateOne) Save(ctx context.Context) (*ChannelMonitor, error) {
|
||||
_u.defaults()
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorUpdateOne) SaveX(ctx context.Context) *ChannelMonitor {
|
||||
node, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Exec executes the query on the entity.
|
||||
func (_u *ChannelMonitorUpdateOne) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorUpdateOne) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (_u *ChannelMonitorUpdateOne) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := channelmonitor.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *ChannelMonitorUpdateOne) check() error {
|
||||
if v, ok := _u.mutation.Name(); ok {
|
||||
if err := channelmonitor.NameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "name", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Provider(); ok {
|
||||
if err := channelmonitor.ProviderValidator(v); err != nil {
|
||||
return &ValidationError{Name: "provider", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.provider": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Endpoint(); ok {
|
||||
if err := channelmonitor.EndpointValidator(v); err != nil {
|
||||
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.endpoint": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.APIKeyEncrypted(); ok {
|
||||
if err := channelmonitor.APIKeyEncryptedValidator(v); err != nil {
|
||||
return &ValidationError{Name: "api_key_encrypted", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.api_key_encrypted": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.PrimaryModel(); ok {
|
||||
if err := channelmonitor.PrimaryModelValidator(v); err != nil {
|
||||
return &ValidationError{Name: "primary_model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.primary_model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.GroupName(); ok {
|
||||
if err := channelmonitor.GroupNameValidator(v); err != nil {
|
||||
return &ValidationError{Name: "group_name", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.group_name": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.IntervalSeconds(); ok {
|
||||
if err := channelmonitor.IntervalSecondsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *ChannelMonitorUpdateOne) sqlSave(ctx context.Context) (_node *ChannelMonitor, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(channelmonitor.Table, channelmonitor.Columns, sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64))
|
||||
id, ok := _u.mutation.ID()
|
||||
if !ok {
|
||||
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ChannelMonitor.id" for update`)}
|
||||
}
|
||||
_spec.Node.ID.Value = id
|
||||
if fields := _u.fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, channelmonitor.FieldID)
|
||||
for _, f := range fields {
|
||||
if !channelmonitor.ValidColumn(f) {
|
||||
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
if f != channelmonitor.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Name(); ok {
|
||||
_spec.SetField(channelmonitor.FieldName, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Provider(); ok {
|
||||
_spec.SetField(channelmonitor.FieldProvider, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Endpoint(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEndpoint, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.APIKeyEncrypted(); ok {
|
||||
_spec.SetField(channelmonitor.FieldAPIKeyEncrypted, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.PrimaryModel(); ok {
|
||||
_spec.SetField(channelmonitor.FieldPrimaryModel, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.ExtraModels(); ok {
|
||||
_spec.SetField(channelmonitor.FieldExtraModels, field.TypeJSON, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AppendedExtraModels(); ok {
|
||||
_spec.AddModifier(func(u *sql.UpdateBuilder) {
|
||||
sqljson.Append(u, channelmonitor.FieldExtraModels, value)
|
||||
})
|
||||
}
|
||||
if value, ok := _u.mutation.GroupName(); ok {
|
||||
_spec.SetField(channelmonitor.FieldGroupName, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.GroupNameCleared() {
|
||||
_spec.ClearField(channelmonitor.FieldGroupName, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.Enabled(); ok {
|
||||
_spec.SetField(channelmonitor.FieldEnabled, field.TypeBool, value)
|
||||
}
|
||||
if value, ok := _u.mutation.IntervalSeconds(); ok {
|
||||
_spec.SetField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LastCheckedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.LastCheckedAtCleared() {
|
||||
_spec.ClearField(channelmonitor.FieldLastCheckedAt, field.TypeTime)
|
||||
}
|
||||
if value, ok := _u.mutation.CreatedBy(); ok {
|
||||
_spec.SetField(channelmonitor.FieldCreatedBy, field.TypeInt64, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedCreatedBy(); ok {
|
||||
_spec.AddField(channelmonitor.FieldCreatedBy, field.TypeInt64, value)
|
||||
}
|
||||
if _u.mutation.HistoryCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.RemovedHistoryIDs(); len(nodes) > 0 && !_u.mutation.HistoryCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.HistoryIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.O2M,
|
||||
Inverse: false,
|
||||
Table: channelmonitor.HistoryTable,
|
||||
Columns: []string{channelmonitor.HistoryColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
_node = &ChannelMonitor{config: _u.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{channelmonitor.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
207
backend/ent/channelmonitorhistory.go
Normal file
207
backend/ent/channelmonitorhistory.go
Normal file
@@ -0,0 +1,207 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistory is the model entity for the ChannelMonitorHistory schema.
|
||||
type ChannelMonitorHistory struct {
|
||||
config `json:"-"`
|
||||
// ID of the ent.
|
||||
ID int64 `json:"id,omitempty"`
|
||||
// MonitorID holds the value of the "monitor_id" field.
|
||||
MonitorID int64 `json:"monitor_id,omitempty"`
|
||||
// Model holds the value of the "model" field.
|
||||
Model string `json:"model,omitempty"`
|
||||
// Status holds the value of the "status" field.
|
||||
Status channelmonitorhistory.Status `json:"status,omitempty"`
|
||||
// LatencyMs holds the value of the "latency_ms" field.
|
||||
LatencyMs *int `json:"latency_ms,omitempty"`
|
||||
// PingLatencyMs holds the value of the "ping_latency_ms" field.
|
||||
PingLatencyMs *int `json:"ping_latency_ms,omitempty"`
|
||||
// Message holds the value of the "message" field.
|
||||
Message string `json:"message,omitempty"`
|
||||
// CheckedAt holds the value of the "checked_at" field.
|
||||
CheckedAt time.Time `json:"checked_at,omitempty"`
|
||||
// Edges holds the relations/edges for other nodes in the graph.
|
||||
// The values are being populated by the ChannelMonitorHistoryQuery when eager-loading is set.
|
||||
Edges ChannelMonitorHistoryEdges `json:"edges"`
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryEdges holds the relations/edges for other nodes in the graph.
|
||||
type ChannelMonitorHistoryEdges struct {
|
||||
// Monitor holds the value of the monitor edge.
|
||||
Monitor *ChannelMonitor `json:"monitor,omitempty"`
|
||||
// loadedTypes holds the information for reporting if a
|
||||
// type was loaded (or requested) in eager-loading or not.
|
||||
loadedTypes [1]bool
|
||||
}
|
||||
|
||||
// MonitorOrErr returns the Monitor value or an error if the edge
|
||||
// was not loaded in eager-loading, or loaded but was not found.
|
||||
func (e ChannelMonitorHistoryEdges) MonitorOrErr() (*ChannelMonitor, error) {
|
||||
if e.Monitor != nil {
|
||||
return e.Monitor, nil
|
||||
} else if e.loadedTypes[0] {
|
||||
return nil, &NotFoundError{label: channelmonitor.Label}
|
||||
}
|
||||
return nil, &NotLoadedError{edge: "monitor"}
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*ChannelMonitorHistory) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case channelmonitorhistory.FieldID, channelmonitorhistory.FieldMonitorID, channelmonitorhistory.FieldLatencyMs, channelmonitorhistory.FieldPingLatencyMs:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case channelmonitorhistory.FieldModel, channelmonitorhistory.FieldStatus, channelmonitorhistory.FieldMessage:
|
||||
values[i] = new(sql.NullString)
|
||||
case channelmonitorhistory.FieldCheckedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the ChannelMonitorHistory fields.
|
||||
func (_m *ChannelMonitorHistory) assignValues(columns []string, values []any) error {
|
||||
if m, n := len(values), len(columns); m < n {
|
||||
return fmt.Errorf("mismatch number of scan values: %d != %d", m, n)
|
||||
}
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case channelmonitorhistory.FieldID:
|
||||
value, ok := values[i].(*sql.NullInt64)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field id", value)
|
||||
}
|
||||
_m.ID = int64(value.Int64)
|
||||
case channelmonitorhistory.FieldMonitorID:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field monitor_id", values[i])
|
||||
} else if value.Valid {
|
||||
_m.MonitorID = value.Int64
|
||||
}
|
||||
case channelmonitorhistory.FieldModel:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field model", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Model = value.String
|
||||
}
|
||||
case channelmonitorhistory.FieldStatus:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field status", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Status = channelmonitorhistory.Status(value.String)
|
||||
}
|
||||
case channelmonitorhistory.FieldLatencyMs:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field latency_ms", values[i])
|
||||
} else if value.Valid {
|
||||
_m.LatencyMs = new(int)
|
||||
*_m.LatencyMs = int(value.Int64)
|
||||
}
|
||||
case channelmonitorhistory.FieldPingLatencyMs:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field ping_latency_ms", values[i])
|
||||
} else if value.Valid {
|
||||
_m.PingLatencyMs = new(int)
|
||||
*_m.PingLatencyMs = int(value.Int64)
|
||||
}
|
||||
case channelmonitorhistory.FieldMessage:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field message", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Message = value.String
|
||||
}
|
||||
case channelmonitorhistory.FieldCheckedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field checked_at", values[i])
|
||||
} else if value.Valid {
|
||||
_m.CheckedAt = value.Time
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns the ent.Value that was dynamically selected and assigned to the ChannelMonitorHistory.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *ChannelMonitorHistory) Value(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// QueryMonitor queries the "monitor" edge of the ChannelMonitorHistory entity.
|
||||
func (_m *ChannelMonitorHistory) QueryMonitor() *ChannelMonitorQuery {
|
||||
return NewChannelMonitorHistoryClient(_m.config).QueryMonitor(_m)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this ChannelMonitorHistory.
|
||||
// Note that you need to call ChannelMonitorHistory.Unwrap() before calling this method if this ChannelMonitorHistory
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *ChannelMonitorHistory) Update() *ChannelMonitorHistoryUpdateOne {
|
||||
return NewChannelMonitorHistoryClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the ChannelMonitorHistory entity that was returned from a transaction after it was closed,
|
||||
// so that all future queries will be executed through the driver which created the transaction.
|
||||
func (_m *ChannelMonitorHistory) Unwrap() *ChannelMonitorHistory {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: ChannelMonitorHistory is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *ChannelMonitorHistory) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("ChannelMonitorHistory(")
|
||||
builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID))
|
||||
builder.WriteString("monitor_id=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.MonitorID))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("model=")
|
||||
builder.WriteString(_m.Model)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("status=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.Status))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.LatencyMs; v != nil {
|
||||
builder.WriteString("latency_ms=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
if v := _m.PingLatencyMs; v != nil {
|
||||
builder.WriteString("ping_latency_ms=")
|
||||
builder.WriteString(fmt.Sprintf("%v", *v))
|
||||
}
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("message=")
|
||||
builder.WriteString(_m.Message)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("checked_at=")
|
||||
builder.WriteString(_m.CheckedAt.Format(time.ANSIC))
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// ChannelMonitorHistories is a parsable slice of ChannelMonitorHistory.
|
||||
type ChannelMonitorHistories []*ChannelMonitorHistory
|
||||
158
backend/ent/channelmonitorhistory/channelmonitorhistory.go
Normal file
158
backend/ent/channelmonitorhistory/channelmonitorhistory.go
Normal file
@@ -0,0 +1,158 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package channelmonitorhistory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the channelmonitorhistory type in the database.
|
||||
Label = "channel_monitor_history"
|
||||
// FieldID holds the string denoting the id field in the database.
|
||||
FieldID = "id"
|
||||
// FieldMonitorID holds the string denoting the monitor_id field in the database.
|
||||
FieldMonitorID = "monitor_id"
|
||||
// FieldModel holds the string denoting the model field in the database.
|
||||
FieldModel = "model"
|
||||
// FieldStatus holds the string denoting the status field in the database.
|
||||
FieldStatus = "status"
|
||||
// FieldLatencyMs holds the string denoting the latency_ms field in the database.
|
||||
FieldLatencyMs = "latency_ms"
|
||||
// FieldPingLatencyMs holds the string denoting the ping_latency_ms field in the database.
|
||||
FieldPingLatencyMs = "ping_latency_ms"
|
||||
// FieldMessage holds the string denoting the message field in the database.
|
||||
FieldMessage = "message"
|
||||
// FieldCheckedAt holds the string denoting the checked_at field in the database.
|
||||
FieldCheckedAt = "checked_at"
|
||||
// EdgeMonitor holds the string denoting the monitor edge name in mutations.
|
||||
EdgeMonitor = "monitor"
|
||||
// Table holds the table name of the channelmonitorhistory in the database.
|
||||
Table = "channel_monitor_histories"
|
||||
// MonitorTable is the table that holds the monitor relation/edge.
|
||||
MonitorTable = "channel_monitor_histories"
|
||||
// MonitorInverseTable is the table name for the ChannelMonitor entity.
|
||||
// It exists in this package in order to avoid circular dependency with the "channelmonitor" package.
|
||||
MonitorInverseTable = "channel_monitors"
|
||||
// MonitorColumn is the table column denoting the monitor relation/edge.
|
||||
MonitorColumn = "monitor_id"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for channelmonitorhistory fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldMonitorID,
|
||||
FieldModel,
|
||||
FieldStatus,
|
||||
FieldLatencyMs,
|
||||
FieldPingLatencyMs,
|
||||
FieldMessage,
|
||||
FieldCheckedAt,
|
||||
}
|
||||
|
||||
// ValidColumn reports if the column name is valid (part of the table columns).
|
||||
func ValidColumn(column string) bool {
|
||||
for i := range Columns {
|
||||
if column == Columns[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
// ModelValidator is a validator for the "model" field. It is called by the builders before save.
|
||||
ModelValidator func(string) error
|
||||
// DefaultMessage holds the default value on creation for the "message" field.
|
||||
DefaultMessage string
|
||||
// MessageValidator is a validator for the "message" field. It is called by the builders before save.
|
||||
MessageValidator func(string) error
|
||||
// DefaultCheckedAt holds the default value on creation for the "checked_at" field.
|
||||
DefaultCheckedAt func() time.Time
|
||||
)
|
||||
|
||||
// Status defines the type for the "status" enum field.
|
||||
type Status string
|
||||
|
||||
// Status values.
|
||||
const (
|
||||
StatusOperational Status = "operational"
|
||||
StatusDegraded Status = "degraded"
|
||||
StatusFailed Status = "failed"
|
||||
StatusError Status = "error"
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// StatusValidator is a validator for the "status" field enum values. It is called by the builders before save.
|
||||
func StatusValidator(s Status) error {
|
||||
switch s {
|
||||
case StatusOperational, StatusDegraded, StatusFailed, StatusError:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("channelmonitorhistory: invalid enum value for status field: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// OrderOption defines the ordering options for the ChannelMonitorHistory queries.
|
||||
type OrderOption func(*sql.Selector)
|
||||
|
||||
// ByID orders the results by the id field.
|
||||
func ByID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByMonitorID orders the results by the monitor_id field.
|
||||
func ByMonitorID(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldMonitorID, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByModel orders the results by the model field.
|
||||
func ByModel(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldModel, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByStatus orders the results by the status field.
|
||||
func ByStatus(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldStatus, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByLatencyMs orders the results by the latency_ms field.
|
||||
func ByLatencyMs(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldLatencyMs, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByPingLatencyMs orders the results by the ping_latency_ms field.
|
||||
func ByPingLatencyMs(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldPingLatencyMs, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByMessage orders the results by the message field.
|
||||
func ByMessage(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldMessage, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByCheckedAt orders the results by the checked_at field.
|
||||
func ByCheckedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldCheckedAt, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByMonitorField orders the results by monitor field.
|
||||
func ByMonitorField(field string, opts ...sql.OrderTermOption) OrderOption {
|
||||
return func(s *sql.Selector) {
|
||||
sqlgraph.OrderByNeighborTerms(s, newMonitorStep(), sql.OrderByField(field, opts...))
|
||||
}
|
||||
}
|
||||
func newMonitorStep() *sqlgraph.Step {
|
||||
return sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.To(MonitorInverseTable, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
|
||||
)
|
||||
}
|
||||
444
backend/ent/channelmonitorhistory/where.go
Normal file
444
backend/ent/channelmonitorhistory/where.go
Normal file
@@ -0,0 +1,444 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package channelmonitorhistory
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldID, id))
|
||||
}
|
||||
|
||||
// MonitorID applies equality check predicate on the "monitor_id" field. It's identical to MonitorIDEQ.
|
||||
func MonitorID(v int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMonitorID, v))
|
||||
}
|
||||
|
||||
// Model applies equality check predicate on the "model" field. It's identical to ModelEQ.
|
||||
func Model(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldModel, v))
|
||||
}
|
||||
|
||||
// LatencyMs applies equality check predicate on the "latency_ms" field. It's identical to LatencyMsEQ.
|
||||
func LatencyMs(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMs applies equality check predicate on the "ping_latency_ms" field. It's identical to PingLatencyMsEQ.
|
||||
func PingLatencyMs(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// Message applies equality check predicate on the "message" field. It's identical to MessageEQ.
|
||||
func Message(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMessage, v))
|
||||
}
|
||||
|
||||
// CheckedAt applies equality check predicate on the "checked_at" field. It's identical to CheckedAtEQ.
|
||||
func CheckedAt(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// MonitorIDEQ applies the EQ predicate on the "monitor_id" field.
|
||||
func MonitorIDEQ(v int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMonitorID, v))
|
||||
}
|
||||
|
||||
// MonitorIDNEQ applies the NEQ predicate on the "monitor_id" field.
|
||||
func MonitorIDNEQ(v int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldMonitorID, v))
|
||||
}
|
||||
|
||||
// MonitorIDIn applies the In predicate on the "monitor_id" field.
|
||||
func MonitorIDIn(vs ...int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldMonitorID, vs...))
|
||||
}
|
||||
|
||||
// MonitorIDNotIn applies the NotIn predicate on the "monitor_id" field.
|
||||
func MonitorIDNotIn(vs ...int64) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldMonitorID, vs...))
|
||||
}
|
||||
|
||||
// ModelEQ applies the EQ predicate on the "model" field.
|
||||
func ModelEQ(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelNEQ applies the NEQ predicate on the "model" field.
|
||||
func ModelNEQ(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelIn applies the In predicate on the "model" field.
|
||||
func ModelIn(vs ...string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldModel, vs...))
|
||||
}
|
||||
|
||||
// ModelNotIn applies the NotIn predicate on the "model" field.
|
||||
func ModelNotIn(vs ...string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldModel, vs...))
|
||||
}
|
||||
|
||||
// ModelGT applies the GT predicate on the "model" field.
|
||||
func ModelGT(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelGTE applies the GTE predicate on the "model" field.
|
||||
func ModelGTE(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelLT applies the LT predicate on the "model" field.
|
||||
func ModelLT(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelLTE applies the LTE predicate on the "model" field.
|
||||
func ModelLTE(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelContains applies the Contains predicate on the "model" field.
|
||||
func ModelContains(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldContains(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelHasPrefix applies the HasPrefix predicate on the "model" field.
|
||||
func ModelHasPrefix(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldHasPrefix(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelHasSuffix applies the HasSuffix predicate on the "model" field.
|
||||
func ModelHasSuffix(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldHasSuffix(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelEqualFold applies the EqualFold predicate on the "model" field.
|
||||
func ModelEqualFold(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEqualFold(FieldModel, v))
|
||||
}
|
||||
|
||||
// ModelContainsFold applies the ContainsFold predicate on the "model" field.
|
||||
func ModelContainsFold(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldContainsFold(FieldModel, v))
|
||||
}
|
||||
|
||||
// StatusEQ applies the EQ predicate on the "status" field.
|
||||
func StatusEQ(v Status) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusNEQ applies the NEQ predicate on the "status" field.
|
||||
func StatusNEQ(v Status) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldStatus, v))
|
||||
}
|
||||
|
||||
// StatusIn applies the In predicate on the "status" field.
|
||||
func StatusIn(vs ...Status) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// StatusNotIn applies the NotIn predicate on the "status" field.
|
||||
func StatusNotIn(vs ...Status) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldStatus, vs...))
|
||||
}
|
||||
|
||||
// LatencyMsEQ applies the EQ predicate on the "latency_ms" field.
|
||||
func LatencyMsEQ(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsNEQ applies the NEQ predicate on the "latency_ms" field.
|
||||
func LatencyMsNEQ(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsIn applies the In predicate on the "latency_ms" field.
|
||||
func LatencyMsIn(vs ...int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldLatencyMs, vs...))
|
||||
}
|
||||
|
||||
// LatencyMsNotIn applies the NotIn predicate on the "latency_ms" field.
|
||||
func LatencyMsNotIn(vs ...int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldLatencyMs, vs...))
|
||||
}
|
||||
|
||||
// LatencyMsGT applies the GT predicate on the "latency_ms" field.
|
||||
func LatencyMsGT(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsGTE applies the GTE predicate on the "latency_ms" field.
|
||||
func LatencyMsGTE(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsLT applies the LT predicate on the "latency_ms" field.
|
||||
func LatencyMsLT(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsLTE applies the LTE predicate on the "latency_ms" field.
|
||||
func LatencyMsLTE(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldLatencyMs, v))
|
||||
}
|
||||
|
||||
// LatencyMsIsNil applies the IsNil predicate on the "latency_ms" field.
|
||||
func LatencyMsIsNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldLatencyMs))
|
||||
}
|
||||
|
||||
// LatencyMsNotNil applies the NotNil predicate on the "latency_ms" field.
|
||||
func LatencyMsNotNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldLatencyMs))
|
||||
}
|
||||
|
||||
// PingLatencyMsEQ applies the EQ predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsEQ(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsNEQ applies the NEQ predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsNEQ(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsIn applies the In predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsIn(vs ...int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldPingLatencyMs, vs...))
|
||||
}
|
||||
|
||||
// PingLatencyMsNotIn applies the NotIn predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsNotIn(vs ...int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldPingLatencyMs, vs...))
|
||||
}
|
||||
|
||||
// PingLatencyMsGT applies the GT predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsGT(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsGTE applies the GTE predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsGTE(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsLT applies the LT predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsLT(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsLTE applies the LTE predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsLTE(v int) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldPingLatencyMs, v))
|
||||
}
|
||||
|
||||
// PingLatencyMsIsNil applies the IsNil predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsIsNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldPingLatencyMs))
|
||||
}
|
||||
|
||||
// PingLatencyMsNotNil applies the NotNil predicate on the "ping_latency_ms" field.
|
||||
func PingLatencyMsNotNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldPingLatencyMs))
|
||||
}
|
||||
|
||||
// MessageEQ applies the EQ predicate on the "message" field.
|
||||
func MessageEQ(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageNEQ applies the NEQ predicate on the "message" field.
|
||||
func MessageNEQ(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageIn applies the In predicate on the "message" field.
|
||||
func MessageIn(vs ...string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldMessage, vs...))
|
||||
}
|
||||
|
||||
// MessageNotIn applies the NotIn predicate on the "message" field.
|
||||
func MessageNotIn(vs ...string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldMessage, vs...))
|
||||
}
|
||||
|
||||
// MessageGT applies the GT predicate on the "message" field.
|
||||
func MessageGT(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageGTE applies the GTE predicate on the "message" field.
|
||||
func MessageGTE(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageLT applies the LT predicate on the "message" field.
|
||||
func MessageLT(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageLTE applies the LTE predicate on the "message" field.
|
||||
func MessageLTE(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageContains applies the Contains predicate on the "message" field.
|
||||
func MessageContains(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldContains(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageHasPrefix applies the HasPrefix predicate on the "message" field.
|
||||
func MessageHasPrefix(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldHasPrefix(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageHasSuffix applies the HasSuffix predicate on the "message" field.
|
||||
func MessageHasSuffix(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldHasSuffix(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageIsNil applies the IsNil predicate on the "message" field.
|
||||
func MessageIsNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIsNull(FieldMessage))
|
||||
}
|
||||
|
||||
// MessageNotNil applies the NotNil predicate on the "message" field.
|
||||
func MessageNotNil() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotNull(FieldMessage))
|
||||
}
|
||||
|
||||
// MessageEqualFold applies the EqualFold predicate on the "message" field.
|
||||
func MessageEqualFold(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEqualFold(FieldMessage, v))
|
||||
}
|
||||
|
||||
// MessageContainsFold applies the ContainsFold predicate on the "message" field.
|
||||
func MessageContainsFold(v string) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldContainsFold(FieldMessage, v))
|
||||
}
|
||||
|
||||
// CheckedAtEQ applies the EQ predicate on the "checked_at" field.
|
||||
func CheckedAtEQ(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldEQ(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// CheckedAtNEQ applies the NEQ predicate on the "checked_at" field.
|
||||
func CheckedAtNEQ(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNEQ(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// CheckedAtIn applies the In predicate on the "checked_at" field.
|
||||
func CheckedAtIn(vs ...time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldIn(FieldCheckedAt, vs...))
|
||||
}
|
||||
|
||||
// CheckedAtNotIn applies the NotIn predicate on the "checked_at" field.
|
||||
func CheckedAtNotIn(vs ...time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldNotIn(FieldCheckedAt, vs...))
|
||||
}
|
||||
|
||||
// CheckedAtGT applies the GT predicate on the "checked_at" field.
|
||||
func CheckedAtGT(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGT(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// CheckedAtGTE applies the GTE predicate on the "checked_at" field.
|
||||
func CheckedAtGTE(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldGTE(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// CheckedAtLT applies the LT predicate on the "checked_at" field.
|
||||
func CheckedAtLT(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLT(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// CheckedAtLTE applies the LTE predicate on the "checked_at" field.
|
||||
func CheckedAtLTE(v time.Time) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.FieldLTE(FieldCheckedAt, v))
|
||||
}
|
||||
|
||||
// HasMonitor applies the HasEdge predicate on the "monitor" edge.
|
||||
func HasMonitor() predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(func(s *sql.Selector) {
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(Table, FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, MonitorTable, MonitorColumn),
|
||||
)
|
||||
sqlgraph.HasNeighbors(s, step)
|
||||
})
|
||||
}
|
||||
|
||||
// HasMonitorWith applies the HasEdge predicate on the "monitor" edge with a given conditions (other predicates).
|
||||
func HasMonitorWith(preds ...predicate.ChannelMonitor) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(func(s *sql.Selector) {
|
||||
step := newMonitorStep()
|
||||
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.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.ChannelMonitorHistory) predicate.ChannelMonitorHistory {
|
||||
return predicate.ChannelMonitorHistory(sql.NotPredicates(p))
|
||||
}
|
||||
947
backend/ent/channelmonitorhistory_create.go
Normal file
947
backend/ent/channelmonitorhistory_create.go
Normal file
@@ -0,0 +1,947 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistoryCreate is the builder for creating a ChannelMonitorHistory entity.
|
||||
type ChannelMonitorHistoryCreate struct {
|
||||
config
|
||||
mutation *ChannelMonitorHistoryMutation
|
||||
hooks []Hook
|
||||
conflict []sql.ConflictOption
|
||||
}
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetMonitorID(v int64) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetMonitorID(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetModel(v string) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetModel(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetStatus(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetLatencyMs(v int) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetLatencyMs(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryCreate {
|
||||
if v != nil {
|
||||
_c.SetLatencyMs(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetPingLatencyMs(v int) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetPingLatencyMs(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryCreate {
|
||||
if v != nil {
|
||||
_c.SetPingLatencyMs(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetMessage(v string) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetMessage(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableMessage sets the "message" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetNillableMessage(v *string) *ChannelMonitorHistoryCreate {
|
||||
if v != nil {
|
||||
_c.SetMessage(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetCheckedAt(v time.Time) *ChannelMonitorHistoryCreate {
|
||||
_c.mutation.SetCheckedAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryCreate {
|
||||
if v != nil {
|
||||
_c.SetCheckedAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
|
||||
func (_c *ChannelMonitorHistoryCreate) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryCreate {
|
||||
return _c.SetMonitorID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
|
||||
func (_c *ChannelMonitorHistoryCreate) Mutation() *ChannelMonitorHistoryMutation {
|
||||
return _c.mutation
|
||||
}
|
||||
|
||||
// Save creates the ChannelMonitorHistory in the database.
|
||||
func (_c *ChannelMonitorHistoryCreate) Save(ctx context.Context) (*ChannelMonitorHistory, error) {
|
||||
_c.defaults()
|
||||
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||
}
|
||||
|
||||
// SaveX calls Save and panics if Save returns an error.
|
||||
func (_c *ChannelMonitorHistoryCreate) SaveX(ctx context.Context) *ChannelMonitorHistory {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *ChannelMonitorHistoryCreate) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *ChannelMonitorHistoryCreate) ExecX(ctx context.Context) {
|
||||
if err := _c.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaults sets the default values of the builder before save.
|
||||
func (_c *ChannelMonitorHistoryCreate) defaults() {
|
||||
if _, ok := _c.mutation.Message(); !ok {
|
||||
v := channelmonitorhistory.DefaultMessage
|
||||
_c.mutation.SetMessage(v)
|
||||
}
|
||||
if _, ok := _c.mutation.CheckedAt(); !ok {
|
||||
v := channelmonitorhistory.DefaultCheckedAt()
|
||||
_c.mutation.SetCheckedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_c *ChannelMonitorHistoryCreate) check() error {
|
||||
if _, ok := _c.mutation.MonitorID(); !ok {
|
||||
return &ValidationError{Name: "monitor_id", err: errors.New(`ent: missing required field "ChannelMonitorHistory.monitor_id"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Model(); !ok {
|
||||
return &ValidationError{Name: "model", err: errors.New(`ent: missing required field "ChannelMonitorHistory.model"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Model(); ok {
|
||||
if err := channelmonitorhistory.ModelValidator(v); err != nil {
|
||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.Status(); !ok {
|
||||
return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "ChannelMonitorHistory.status"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Status(); ok {
|
||||
if err := channelmonitorhistory.StatusValidator(v); err != nil {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _c.mutation.Message(); ok {
|
||||
if err := channelmonitorhistory.MessageValidator(v); err != nil {
|
||||
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.CheckedAt(); !ok {
|
||||
return &ValidationError{Name: "checked_at", err: errors.New(`ent: missing required field "ChannelMonitorHistory.checked_at"`)}
|
||||
}
|
||||
if len(_c.mutation.MonitorIDs()) == 0 {
|
||||
return &ValidationError{Name: "monitor", err: errors.New(`ent: missing required edge "ChannelMonitorHistory.monitor"`)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_c *ChannelMonitorHistoryCreate) sqlSave(ctx context.Context) (*ChannelMonitorHistory, error) {
|
||||
if err := _c.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_node, _spec := _c.createSpec()
|
||||
if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
id := _spec.ID.Value.(int64)
|
||||
_node.ID = int64(id)
|
||||
_c.mutation.id = &_node.ID
|
||||
_c.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
func (_c *ChannelMonitorHistoryCreate) createSpec() (*ChannelMonitorHistory, *sqlgraph.CreateSpec) {
|
||||
var (
|
||||
_node = &ChannelMonitorHistory{config: _c.config}
|
||||
_spec = sqlgraph.NewCreateSpec(channelmonitorhistory.Table, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
|
||||
)
|
||||
_spec.OnConflict = _c.conflict
|
||||
if value, ok := _c.mutation.Model(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
|
||||
_node.Model = value
|
||||
}
|
||||
if value, ok := _c.mutation.Status(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
|
||||
_node.Status = value
|
||||
}
|
||||
if value, ok := _c.mutation.LatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
|
||||
_node.LatencyMs = &value
|
||||
}
|
||||
if value, ok := _c.mutation.PingLatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
|
||||
_node.PingLatencyMs = &value
|
||||
}
|
||||
if value, ok := _c.mutation.Message(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
|
||||
_node.Message = value
|
||||
}
|
||||
if value, ok := _c.mutation.CheckedAt(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
|
||||
_node.CheckedAt = value
|
||||
}
|
||||
if nodes := _c.mutation.MonitorIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: channelmonitorhistory.MonitorTable,
|
||||
Columns: []string{channelmonitorhistory.MonitorColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_node.MonitorID = nodes[0]
|
||||
_spec.Edges = append(_spec.Edges, edge)
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
|
||||
// of the `INSERT` statement. For example:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// SetMonitorID(v).
|
||||
// OnConflict(
|
||||
// // Update the row with the new values
|
||||
// // the was proposed for insertion.
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// // Override some of the fields with custom
|
||||
// // update values.
|
||||
// Update(func(u *ent.ChannelMonitorHistoryUpsert) {
|
||||
// SetMonitorID(v+v).
|
||||
// }).
|
||||
// Exec(ctx)
|
||||
func (_c *ChannelMonitorHistoryCreate) OnConflict(opts ...sql.ConflictOption) *ChannelMonitorHistoryUpsertOne {
|
||||
_c.conflict = opts
|
||||
return &ChannelMonitorHistoryUpsertOne{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||
// as conflict target. Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(sql.ConflictColumns(columns...)).
|
||||
// Exec(ctx)
|
||||
func (_c *ChannelMonitorHistoryCreate) OnConflictColumns(columns ...string) *ChannelMonitorHistoryUpsertOne {
|
||||
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||
return &ChannelMonitorHistoryUpsertOne{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// ChannelMonitorHistoryUpsertOne is the builder for "upsert"-ing
|
||||
// one ChannelMonitorHistory node.
|
||||
ChannelMonitorHistoryUpsertOne struct {
|
||||
create *ChannelMonitorHistoryCreate
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryUpsert is the "OnConflict" setter.
|
||||
ChannelMonitorHistoryUpsert struct {
|
||||
*sql.UpdateSet
|
||||
}
|
||||
)
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetMonitorID(v int64) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldMonitorID, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateMonitorID() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldMonitorID)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetModel(v string) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldModel, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateModel sets the "model" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateModel() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldModel)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldStatus, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateStatus sets the "status" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateStatus() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldStatus)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetLatencyMs(v int) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldLatencyMs, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateLatencyMs() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldLatencyMs)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddLatencyMs adds v to the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) AddLatencyMs(v int) *ChannelMonitorHistoryUpsert {
|
||||
u.Add(channelmonitorhistory.FieldLatencyMs, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearLatencyMs clears the value of the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) ClearLatencyMs() *ChannelMonitorHistoryUpsert {
|
||||
u.SetNull(channelmonitorhistory.FieldLatencyMs)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldPingLatencyMs, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldPingLatencyMs)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsert {
|
||||
u.Add(channelmonitorhistory.FieldPingLatencyMs, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) ClearPingLatencyMs() *ChannelMonitorHistoryUpsert {
|
||||
u.SetNull(channelmonitorhistory.FieldPingLatencyMs)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetMessage(v string) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldMessage, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateMessage sets the "message" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateMessage() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldMessage)
|
||||
return u
|
||||
}
|
||||
|
||||
// ClearMessage clears the value of the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) ClearMessage() *ChannelMonitorHistoryUpsert {
|
||||
u.SetNull(channelmonitorhistory.FieldMessage)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (u *ChannelMonitorHistoryUpsert) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsert {
|
||||
u.Set(channelmonitorhistory.FieldCheckedAt, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsert) UpdateCheckedAt() *ChannelMonitorHistoryUpsert {
|
||||
u.SetExcluded(channelmonitorhistory.FieldCheckedAt)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// Exec(ctx)
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateNewValues() *ChannelMonitorHistoryUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||
return u
|
||||
}
|
||||
|
||||
// Ignore sets each column to itself in case of conflict.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(sql.ResolveWithIgnore()).
|
||||
// Exec(ctx)
|
||||
func (u *ChannelMonitorHistoryUpsertOne) Ignore() *ChannelMonitorHistoryUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
|
||||
return u
|
||||
}
|
||||
|
||||
// DoNothing configures the conflict_action to `DO NOTHING`.
|
||||
// Supported only by SQLite and PostgreSQL.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) DoNothing() *ChannelMonitorHistoryUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||
return u
|
||||
}
|
||||
|
||||
// Update allows overriding fields `UPDATE` values. See the ChannelMonitorHistoryCreate.OnConflict
|
||||
// documentation for more info.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) Update(set func(*ChannelMonitorHistoryUpsert)) *ChannelMonitorHistoryUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||
set(&ChannelMonitorHistoryUpsert{UpdateSet: update})
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetMonitorID(v int64) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetMonitorID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateMonitorID() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateMonitorID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetModel(v string) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetModel(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateModel sets the "model" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateModel() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateModel()
|
||||
})
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetStatus(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateStatus sets the "status" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateStatus() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateStatus()
|
||||
})
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddLatencyMs adds v to the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) AddLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.AddLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateLatencyMs() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearLatencyMs clears the value of the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) ClearLatencyMs() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetPingLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.AddPingLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdatePingLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) ClearPingLatencyMs() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearPingLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetMessage(v string) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetMessage(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMessage sets the "message" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateMessage() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateMessage()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearMessage clears the value of the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) ClearMessage() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearMessage()
|
||||
})
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetCheckedAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) UpdateCheckedAt() *ChannelMonitorHistoryUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateCheckedAt()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
return errors.New("ent: missing options for ChannelMonitorHistoryCreate.OnConflict")
|
||||
}
|
||||
return u.create.Exec(ctx)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) ExecX(ctx context.Context) {
|
||||
if err := u.create.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Exec executes the UPSERT query and returns the inserted/updated ID.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) ID(ctx context.Context) (id int64, err error) {
|
||||
node, err := u.create.Save(ctx)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
return node.ID, nil
|
||||
}
|
||||
|
||||
// IDX is like ID, but panics if an error occurs.
|
||||
func (u *ChannelMonitorHistoryUpsertOne) IDX(ctx context.Context) int64 {
|
||||
id, err := u.ID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryCreateBulk is the builder for creating many ChannelMonitorHistory entities in bulk.
|
||||
type ChannelMonitorHistoryCreateBulk struct {
|
||||
config
|
||||
err error
|
||||
builders []*ChannelMonitorHistoryCreate
|
||||
conflict []sql.ConflictOption
|
||||
}
|
||||
|
||||
// Save creates the ChannelMonitorHistory entities in the database.
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) Save(ctx context.Context) ([]*ChannelMonitorHistory, error) {
|
||||
if _c.err != nil {
|
||||
return nil, _c.err
|
||||
}
|
||||
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||
nodes := make([]*ChannelMonitorHistory, len(_c.builders))
|
||||
mutators := make([]Mutator, len(_c.builders))
|
||||
for i := range _c.builders {
|
||||
func(i int, root context.Context) {
|
||||
builder := _c.builders[i]
|
||||
builder.defaults()
|
||||
var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {
|
||||
mutation, ok := m.(*ChannelMonitorHistoryMutation)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected mutation type %T", m)
|
||||
}
|
||||
if err := builder.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builder.mutation = mutation
|
||||
var err error
|
||||
nodes[i], specs[i] = builder.createSpec()
|
||||
if i < len(mutators)-1 {
|
||||
_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)
|
||||
} else {
|
||||
spec := &sqlgraph.BatchCreateSpec{Nodes: specs}
|
||||
spec.OnConflict = _c.conflict
|
||||
// Invoke the actual operation on the latest mutation in the chain.
|
||||
if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {
|
||||
if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mutation.id = &nodes[i].ID
|
||||
if specs[i].ID.Value != nil {
|
||||
id := specs[i].ID.Value.(int64)
|
||||
nodes[i].ID = int64(id)
|
||||
}
|
||||
mutation.done = true
|
||||
return nodes[i], nil
|
||||
})
|
||||
for i := len(builder.hooks) - 1; i >= 0; i-- {
|
||||
mut = builder.hooks[i](mut)
|
||||
}
|
||||
mutators[i] = mut
|
||||
}(i, ctx)
|
||||
}
|
||||
if len(mutators) > 0 {
|
||||
if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) SaveX(ctx context.Context) []*ChannelMonitorHistory {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) ExecX(ctx context.Context) {
|
||||
if err := _c.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
|
||||
// of the `INSERT` statement. For example:
|
||||
//
|
||||
// client.ChannelMonitorHistory.CreateBulk(builders...).
|
||||
// OnConflict(
|
||||
// // Update the row with the new values
|
||||
// // the was proposed for insertion.
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// // Override some of the fields with custom
|
||||
// // update values.
|
||||
// Update(func(u *ent.ChannelMonitorHistoryUpsert) {
|
||||
// SetMonitorID(v+v).
|
||||
// }).
|
||||
// Exec(ctx)
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) OnConflict(opts ...sql.ConflictOption) *ChannelMonitorHistoryUpsertBulk {
|
||||
_c.conflict = opts
|
||||
return &ChannelMonitorHistoryUpsertBulk{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||
// as conflict target. Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(sql.ConflictColumns(columns...)).
|
||||
// Exec(ctx)
|
||||
func (_c *ChannelMonitorHistoryCreateBulk) OnConflictColumns(columns ...string) *ChannelMonitorHistoryUpsertBulk {
|
||||
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||
return &ChannelMonitorHistoryUpsertBulk{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryUpsertBulk is the builder for "upsert"-ing
|
||||
// a bulk of ChannelMonitorHistory nodes.
|
||||
type ChannelMonitorHistoryUpsertBulk struct {
|
||||
create *ChannelMonitorHistoryCreateBulk
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that
|
||||
// were set on create. Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// Exec(ctx)
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateNewValues() *ChannelMonitorHistoryUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||
return u
|
||||
}
|
||||
|
||||
// Ignore sets each column to itself in case of conflict.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.ChannelMonitorHistory.Create().
|
||||
// OnConflict(sql.ResolveWithIgnore()).
|
||||
// Exec(ctx)
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) Ignore() *ChannelMonitorHistoryUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore())
|
||||
return u
|
||||
}
|
||||
|
||||
// DoNothing configures the conflict_action to `DO NOTHING`.
|
||||
// Supported only by SQLite and PostgreSQL.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) DoNothing() *ChannelMonitorHistoryUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||
return u
|
||||
}
|
||||
|
||||
// Update allows overriding fields `UPDATE` values. See the ChannelMonitorHistoryCreateBulk.OnConflict
|
||||
// documentation for more info.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) Update(set func(*ChannelMonitorHistoryUpsert)) *ChannelMonitorHistoryUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||
set(&ChannelMonitorHistoryUpsert{UpdateSet: update})
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetMonitorID(v int64) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetMonitorID(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMonitorID sets the "monitor_id" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateMonitorID() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateMonitorID()
|
||||
})
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetModel(v string) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetModel(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateModel sets the "model" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateModel() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateModel()
|
||||
})
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetStatus(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateStatus sets the "status" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateStatus() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateStatus()
|
||||
})
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddLatencyMs adds v to the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) AddLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.AddLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateLatencyMs sets the "latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateLatencyMs() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearLatencyMs clears the value of the "latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) ClearLatencyMs() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetPingLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddPingLatencyMs adds v to the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.AddPingLatencyMs(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdatePingLatencyMs sets the "ping_latency_ms" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdatePingLatencyMs() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdatePingLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) ClearPingLatencyMs() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearPingLatencyMs()
|
||||
})
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetMessage(v string) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetMessage(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateMessage sets the "message" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateMessage() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateMessage()
|
||||
})
|
||||
}
|
||||
|
||||
// ClearMessage clears the value of the "message" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) ClearMessage() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.ClearMessage()
|
||||
})
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.SetCheckedAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateCheckedAt sets the "checked_at" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) UpdateCheckedAt() *ChannelMonitorHistoryUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorHistoryUpsert) {
|
||||
s.UpdateCheckedAt()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) Exec(ctx context.Context) error {
|
||||
if u.create.err != nil {
|
||||
return u.create.err
|
||||
}
|
||||
for i, b := range u.create.builders {
|
||||
if len(b.conflict) != 0 {
|
||||
return fmt.Errorf("ent: OnConflict was set for builder %d. Set it on the ChannelMonitorHistoryCreateBulk instead", i)
|
||||
}
|
||||
}
|
||||
if len(u.create.conflict) == 0 {
|
||||
return errors.New("ent: missing options for ChannelMonitorHistoryCreateBulk.OnConflict")
|
||||
}
|
||||
return u.create.Exec(ctx)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (u *ChannelMonitorHistoryUpsertBulk) ExecX(ctx context.Context) {
|
||||
if err := u.create.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
88
backend/ent/channelmonitorhistory_delete.go
Normal file
88
backend/ent/channelmonitorhistory_delete.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistoryDelete is the builder for deleting a ChannelMonitorHistory entity.
|
||||
type ChannelMonitorHistoryDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorHistoryMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorHistoryDelete builder.
|
||||
func (_d *ChannelMonitorHistoryDelete) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryDelete {
|
||||
_d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (_d *ChannelMonitorHistoryDelete) Exec(ctx context.Context) (int, error) {
|
||||
return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *ChannelMonitorHistoryDelete) ExecX(ctx context.Context) int {
|
||||
n, err := _d.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (_d *ChannelMonitorHistoryDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := sqlgraph.NewDeleteSpec(channelmonitorhistory.Table, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
|
||||
if ps := _d.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)
|
||||
if err != nil && sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
_d.mutation.done = true
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryDeleteOne is the builder for deleting a single ChannelMonitorHistory entity.
|
||||
type ChannelMonitorHistoryDeleteOne struct {
|
||||
_d *ChannelMonitorHistoryDelete
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorHistoryDelete builder.
|
||||
func (_d *ChannelMonitorHistoryDeleteOne) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryDeleteOne {
|
||||
_d._d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (_d *ChannelMonitorHistoryDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := _d._d.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{channelmonitorhistory.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *ChannelMonitorHistoryDeleteOne) ExecX(ctx context.Context) {
|
||||
if err := _d.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
643
backend/ent/channelmonitorhistory_query.go
Normal file
643
backend/ent/channelmonitorhistory_query.go
Normal file
@@ -0,0 +1,643 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistoryQuery is the builder for querying ChannelMonitorHistory entities.
|
||||
type ChannelMonitorHistoryQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []channelmonitorhistory.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.ChannelMonitorHistory
|
||||
withMonitor *ChannelMonitorQuery
|
||||
modifiers []func(*sql.Selector)
|
||||
// intermediate query (i.e. traversal path).
|
||||
sql *sql.Selector
|
||||
path func(context.Context) (*sql.Selector, error)
|
||||
}
|
||||
|
||||
// Where adds a new predicate for the ChannelMonitorHistoryQuery builder.
|
||||
func (_q *ChannelMonitorHistoryQuery) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryQuery {
|
||||
_q.predicates = append(_q.predicates, ps...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// Limit the number of records to be returned by this query.
|
||||
func (_q *ChannelMonitorHistoryQuery) Limit(limit int) *ChannelMonitorHistoryQuery {
|
||||
_q.ctx.Limit = &limit
|
||||
return _q
|
||||
}
|
||||
|
||||
// Offset to start from.
|
||||
func (_q *ChannelMonitorHistoryQuery) Offset(offset int) *ChannelMonitorHistoryQuery {
|
||||
_q.ctx.Offset = &offset
|
||||
return _q
|
||||
}
|
||||
|
||||
// Unique configures the query builder to filter duplicate records on query.
|
||||
// By default, unique is set to true, and can be disabled using this method.
|
||||
func (_q *ChannelMonitorHistoryQuery) Unique(unique bool) *ChannelMonitorHistoryQuery {
|
||||
_q.ctx.Unique = &unique
|
||||
return _q
|
||||
}
|
||||
|
||||
// Order specifies how the records should be ordered.
|
||||
func (_q *ChannelMonitorHistoryQuery) Order(o ...channelmonitorhistory.OrderOption) *ChannelMonitorHistoryQuery {
|
||||
_q.order = append(_q.order, o...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// QueryMonitor chains the current query on the "monitor" edge.
|
||||
func (_q *ChannelMonitorHistoryQuery) QueryMonitor() *ChannelMonitorQuery {
|
||||
query := (&ChannelMonitorClient{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(channelmonitorhistory.Table, channelmonitorhistory.FieldID, selector),
|
||||
sqlgraph.To(channelmonitor.Table, channelmonitor.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, channelmonitorhistory.MonitorTable, channelmonitorhistory.MonitorColumn),
|
||||
)
|
||||
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
|
||||
return fromU, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// First returns the first ChannelMonitorHistory entity from the query.
|
||||
// Returns a *NotFoundError when no ChannelMonitorHistory was found.
|
||||
func (_q *ChannelMonitorHistoryQuery) First(ctx context.Context) (*ChannelMonitorHistory, error) {
|
||||
nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nil, &NotFoundError{channelmonitorhistory.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) FirstX(ctx context.Context) *ChannelMonitorHistory {
|
||||
node, err := _q.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first ChannelMonitorHistory ID from the query.
|
||||
// Returns a *NotFoundError when no ChannelMonitorHistory ID was found.
|
||||
func (_q *ChannelMonitorHistoryQuery) FirstID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {
|
||||
return
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
err = &NotFoundError{channelmonitorhistory.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) FirstIDX(ctx context.Context) int64 {
|
||||
id, err := _q.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single ChannelMonitorHistory entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one ChannelMonitorHistory entity is found.
|
||||
// Returns a *NotFoundError when no ChannelMonitorHistory entities are found.
|
||||
func (_q *ChannelMonitorHistoryQuery) Only(ctx context.Context) (*ChannelMonitorHistory, error) {
|
||||
nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch len(nodes) {
|
||||
case 1:
|
||||
return nodes[0], nil
|
||||
case 0:
|
||||
return nil, &NotFoundError{channelmonitorhistory.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{channelmonitorhistory.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) OnlyX(ctx context.Context) *ChannelMonitorHistory {
|
||||
node, err := _q.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only ChannelMonitorHistory ID in the query.
|
||||
// Returns a *NotSingularError when more than one ChannelMonitorHistory ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (_q *ChannelMonitorHistoryQuery) OnlyID(ctx context.Context) (id int64, err error) {
|
||||
var ids []int64
|
||||
if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {
|
||||
return
|
||||
}
|
||||
switch len(ids) {
|
||||
case 1:
|
||||
id = ids[0]
|
||||
case 0:
|
||||
err = &NotFoundError{channelmonitorhistory.Label}
|
||||
default:
|
||||
err = &NotSingularError{channelmonitorhistory.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) OnlyIDX(ctx context.Context) int64 {
|
||||
id, err := _q.OnlyID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// All executes the query and returns a list of ChannelMonitorHistories.
|
||||
func (_q *ChannelMonitorHistoryQuery) All(ctx context.Context) ([]*ChannelMonitorHistory, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr := querierAll[[]*ChannelMonitorHistory, *ChannelMonitorHistoryQuery]()
|
||||
return withInterceptors[[]*ChannelMonitorHistory](ctx, _q, qr, _q.inters)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) AllX(ctx context.Context) []*ChannelMonitorHistory {
|
||||
nodes, err := _q.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of ChannelMonitorHistory IDs.
|
||||
func (_q *ChannelMonitorHistoryQuery) IDs(ctx context.Context) (ids []int64, err error) {
|
||||
if _q.ctx.Unique == nil && _q.path != nil {
|
||||
_q.Unique(true)
|
||||
}
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)
|
||||
if err = _q.Select(channelmonitorhistory.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) IDsX(ctx context.Context) []int64 {
|
||||
ids, err := _q.IDs(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// Count returns the count of the given query.
|
||||
func (_q *ChannelMonitorHistoryQuery) Count(ctx context.Context) (int, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return withInterceptors[int](ctx, _q, querierCount[*ChannelMonitorHistoryQuery](), _q.inters)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) CountX(ctx context.Context) int {
|
||||
count, err := _q.Count(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Exist returns true if the query has elements in the graph.
|
||||
func (_q *ChannelMonitorHistoryQuery) Exist(ctx context.Context) (bool, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)
|
||||
switch _, err := _q.FirstID(ctx); {
|
||||
case IsNotFound(err):
|
||||
return false, nil
|
||||
case err != nil:
|
||||
return false, fmt.Errorf("ent: check existence: %w", err)
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExistX is like Exist, but panics if an error occurs.
|
||||
func (_q *ChannelMonitorHistoryQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := _q.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the ChannelMonitorHistoryQuery builder, including all associated steps. It can be
|
||||
// used to prepare common query builders and use them differently after the clone is made.
|
||||
func (_q *ChannelMonitorHistoryQuery) Clone() *ChannelMonitorHistoryQuery {
|
||||
if _q == nil {
|
||||
return nil
|
||||
}
|
||||
return &ChannelMonitorHistoryQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]channelmonitorhistory.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.ChannelMonitorHistory{}, _q.predicates...),
|
||||
withMonitor: _q.withMonitor.Clone(),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMonitor tells the query-builder to eager-load the nodes that are connected to
|
||||
// the "monitor" edge. The optional arguments are used to configure the query builder of the edge.
|
||||
func (_q *ChannelMonitorHistoryQuery) WithMonitor(opts ...func(*ChannelMonitorQuery)) *ChannelMonitorHistoryQuery {
|
||||
query := (&ChannelMonitorClient{config: _q.config}).Query()
|
||||
for _, opt := range opts {
|
||||
opt(query)
|
||||
}
|
||||
_q.withMonitor = query
|
||||
return _q
|
||||
}
|
||||
|
||||
// GroupBy is used to group vertices by one or more fields/columns.
|
||||
// It is often used with aggregate functions, like: count, max, mean, min, sum.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// MonitorID int64 `json:"monitor_id,omitempty"`
|
||||
// Count int `json:"count,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ChannelMonitorHistory.Query().
|
||||
// GroupBy(channelmonitorhistory.FieldMonitorID).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ChannelMonitorHistoryQuery) GroupBy(field string, fields ...string) *ChannelMonitorHistoryGroupBy {
|
||||
_q.ctx.Fields = append([]string{field}, fields...)
|
||||
grbuild := &ChannelMonitorHistoryGroupBy{build: _q}
|
||||
grbuild.flds = &_q.ctx.Fields
|
||||
grbuild.label = channelmonitorhistory.Label
|
||||
grbuild.scan = grbuild.Scan
|
||||
return grbuild
|
||||
}
|
||||
|
||||
// Select allows the selection one or more fields/columns for the given query,
|
||||
// instead of selecting all fields in the entity.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var v []struct {
|
||||
// MonitorID int64 `json:"monitor_id,omitempty"`
|
||||
// }
|
||||
//
|
||||
// client.ChannelMonitorHistory.Query().
|
||||
// Select(channelmonitorhistory.FieldMonitorID).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *ChannelMonitorHistoryQuery) Select(fields ...string) *ChannelMonitorHistorySelect {
|
||||
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||
sbuild := &ChannelMonitorHistorySelect{ChannelMonitorHistoryQuery: _q}
|
||||
sbuild.label = channelmonitorhistory.Label
|
||||
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||
return sbuild
|
||||
}
|
||||
|
||||
// Aggregate returns a ChannelMonitorHistorySelect configured with the given aggregations.
|
||||
func (_q *ChannelMonitorHistoryQuery) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistorySelect {
|
||||
return _q.Select().Aggregate(fns...)
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) prepareQuery(ctx context.Context) error {
|
||||
for _, inter := range _q.inters {
|
||||
if inter == nil {
|
||||
return fmt.Errorf("ent: uninitialized interceptor (forgotten import ent/runtime?)")
|
||||
}
|
||||
if trv, ok := inter.(Traverser); ok {
|
||||
if err := trv.Traverse(ctx, _q); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range _q.ctx.Fields {
|
||||
if !channelmonitorhistory.ValidColumn(f) {
|
||||
return &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
}
|
||||
if _q.path != nil {
|
||||
prev, err := _q.path(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_q.sql = prev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*ChannelMonitorHistory, error) {
|
||||
var (
|
||||
nodes = []*ChannelMonitorHistory{}
|
||||
_spec = _q.querySpec()
|
||||
loadedTypes = [1]bool{
|
||||
_q.withMonitor != nil,
|
||||
}
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*ChannelMonitorHistory).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &ChannelMonitorHistory{config: _q.config}
|
||||
nodes = append(nodes, node)
|
||||
node.Edges.loadedTypes = loadedTypes
|
||||
return node.assignValues(columns, values)
|
||||
}
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
for i := range hooks {
|
||||
hooks[i](ctx, _spec)
|
||||
}
|
||||
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
if query := _q.withMonitor; query != nil {
|
||||
if err := _q.loadMonitor(ctx, query, nodes, nil,
|
||||
func(n *ChannelMonitorHistory, e *ChannelMonitor) { n.Edges.Monitor = e }); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) loadMonitor(ctx context.Context, query *ChannelMonitorQuery, nodes []*ChannelMonitorHistory, init func(*ChannelMonitorHistory), assign func(*ChannelMonitorHistory, *ChannelMonitor)) error {
|
||||
ids := make([]int64, 0, len(nodes))
|
||||
nodeids := make(map[int64][]*ChannelMonitorHistory)
|
||||
for i := range nodes {
|
||||
fk := nodes[i].MonitorID
|
||||
if _, ok := nodeids[fk]; !ok {
|
||||
ids = append(ids, fk)
|
||||
}
|
||||
nodeids[fk] = append(nodeids[fk], nodes[i])
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
query.Where(channelmonitor.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 "monitor_id" returned %v`, n.ID)
|
||||
}
|
||||
for i := range nodes {
|
||||
assign(nodes[i], n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) sqlCount(ctx context.Context) (int, error) {
|
||||
_spec := _q.querySpec()
|
||||
if len(_q.modifiers) > 0 {
|
||||
_spec.Modifiers = _q.modifiers
|
||||
}
|
||||
_spec.Node.Columns = _q.ctx.Fields
|
||||
if len(_q.ctx.Fields) > 0 {
|
||||
_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique
|
||||
}
|
||||
return sqlgraph.CountNodes(ctx, _q.driver, _spec)
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := sqlgraph.NewQuerySpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
|
||||
_spec.From = _q.sql
|
||||
if unique := _q.ctx.Unique; unique != nil {
|
||||
_spec.Unique = *unique
|
||||
} else if _q.path != nil {
|
||||
_spec.Unique = true
|
||||
}
|
||||
if fields := _q.ctx.Fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, channelmonitorhistory.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != channelmonitorhistory.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
|
||||
}
|
||||
}
|
||||
if _q.withMonitor != nil {
|
||||
_spec.Node.AddColumnOnce(channelmonitorhistory.FieldMonitorID)
|
||||
}
|
||||
}
|
||||
if ps := _q.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
_spec.Limit = *limit
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
_spec.Offset = *offset
|
||||
}
|
||||
if ps := _q.order; len(ps) > 0 {
|
||||
_spec.Order = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
return _spec
|
||||
}
|
||||
|
||||
func (_q *ChannelMonitorHistoryQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(_q.driver.Dialect())
|
||||
t1 := builder.Table(channelmonitorhistory.Table)
|
||||
columns := _q.ctx.Fields
|
||||
if len(columns) == 0 {
|
||||
columns = channelmonitorhistory.Columns
|
||||
}
|
||||
selector := builder.Select(t1.Columns(columns...)...).From(t1)
|
||||
if _q.sql != nil {
|
||||
selector = _q.sql
|
||||
selector.Select(selector.Columns(columns...)...)
|
||||
}
|
||||
if _q.ctx.Unique != nil && *_q.ctx.Unique {
|
||||
selector.Distinct()
|
||||
}
|
||||
for _, m := range _q.modifiers {
|
||||
m(selector)
|
||||
}
|
||||
for _, p := range _q.predicates {
|
||||
p(selector)
|
||||
}
|
||||
for _, p := range _q.order {
|
||||
p(selector)
|
||||
}
|
||||
if offset := _q.ctx.Offset; offset != nil {
|
||||
// limit is mandatory for offset clause. We start
|
||||
// with default value, and override it below if needed.
|
||||
selector.Offset(*offset).Limit(math.MaxInt32)
|
||||
}
|
||||
if limit := _q.ctx.Limit; limit != nil {
|
||||
selector.Limit(*limit)
|
||||
}
|
||||
return selector
|
||||
}
|
||||
|
||||
// ForUpdate locks the selected rows against concurrent updates, and prevent them from being
|
||||
// updated, deleted or "selected ... for update" by other sessions, until the transaction is
|
||||
// either committed or rolled-back.
|
||||
func (_q *ChannelMonitorHistoryQuery) ForUpdate(opts ...sql.LockOption) *ChannelMonitorHistoryQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForUpdate(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock
|
||||
// on any rows that are read. Other sessions can read the rows, but cannot modify them
|
||||
// until your transaction commits.
|
||||
func (_q *ChannelMonitorHistoryQuery) ForShare(opts ...sql.LockOption) *ChannelMonitorHistoryQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForShare(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryGroupBy is the group-by builder for ChannelMonitorHistory entities.
|
||||
type ChannelMonitorHistoryGroupBy struct {
|
||||
selector
|
||||
build *ChannelMonitorHistoryQuery
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (_g *ChannelMonitorHistoryGroupBy) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistoryGroupBy {
|
||||
_g.fns = append(_g.fns, fns...)
|
||||
return _g
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_g *ChannelMonitorHistoryGroupBy) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)
|
||||
if err := _g.build.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*ChannelMonitorHistoryQuery, *ChannelMonitorHistoryGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||
}
|
||||
|
||||
func (_g *ChannelMonitorHistoryGroupBy) sqlScan(ctx context.Context, root *ChannelMonitorHistoryQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx).Select()
|
||||
aggregation := make([]string, 0, len(_g.fns))
|
||||
for _, fn := range _g.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
if len(selector.SelectedColumns()) == 0 {
|
||||
columns := make([]string, 0, len(*_g.flds)+len(_g.fns))
|
||||
for _, f := range *_g.flds {
|
||||
columns = append(columns, selector.C(f))
|
||||
}
|
||||
columns = append(columns, aggregation...)
|
||||
selector.Select(columns...)
|
||||
}
|
||||
selector.GroupBy(selector.Columns(*_g.flds...)...)
|
||||
if err := selector.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _g.build.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
|
||||
// ChannelMonitorHistorySelect is the builder for selecting fields of ChannelMonitorHistory entities.
|
||||
type ChannelMonitorHistorySelect struct {
|
||||
*ChannelMonitorHistoryQuery
|
||||
selector
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the selector query.
|
||||
func (_s *ChannelMonitorHistorySelect) Aggregate(fns ...AggregateFunc) *ChannelMonitorHistorySelect {
|
||||
_s.fns = append(_s.fns, fns...)
|
||||
return _s
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_s *ChannelMonitorHistorySelect) Scan(ctx context.Context, v any) error {
|
||||
ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)
|
||||
if err := _s.prepareQuery(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return scanWithInterceptors[*ChannelMonitorHistoryQuery, *ChannelMonitorHistorySelect](ctx, _s.ChannelMonitorHistoryQuery, _s, _s.inters, v)
|
||||
}
|
||||
|
||||
func (_s *ChannelMonitorHistorySelect) sqlScan(ctx context.Context, root *ChannelMonitorHistoryQuery, v any) error {
|
||||
selector := root.sqlQuery(ctx)
|
||||
aggregation := make([]string, 0, len(_s.fns))
|
||||
for _, fn := range _s.fns {
|
||||
aggregation = append(aggregation, fn(selector))
|
||||
}
|
||||
switch n := len(*_s.selector.flds); {
|
||||
case n == 0 && len(aggregation) > 0:
|
||||
selector.Select(aggregation...)
|
||||
case n != 0 && len(aggregation) > 0:
|
||||
selector.AppendSelect(aggregation...)
|
||||
}
|
||||
rows := &sql.Rows{}
|
||||
query, args := selector.Query()
|
||||
if err := _s.driver.Query(ctx, query, args, rows); err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
return sql.ScanSlice(rows, v)
|
||||
}
|
||||
635
backend/ent/channelmonitorhistory_update.go
Normal file
635
backend/ent/channelmonitorhistory_update.go
Normal file
@@ -0,0 +1,635 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistoryUpdate is the builder for updating ChannelMonitorHistory entities.
|
||||
type ChannelMonitorHistoryUpdate struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorHistoryMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorHistoryUpdate builder.
|
||||
func (_u *ChannelMonitorHistoryUpdate) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetMonitorID(v int64) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.SetMonitorID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableMonitorID(v *int64) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetMonitorID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetModel(v string) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.SetModel(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableModel sets the "model" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableModel(v *string) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetModel(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.SetStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableStatus(v *channelmonitorhistory.Status) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetLatencyMs(v int) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ResetLatencyMs()
|
||||
_u.mutation.SetLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetLatencyMs(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddLatencyMs adds value to the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) AddLatencyMs(v int) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.AddLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLatencyMs clears the value of the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) ClearLatencyMs() *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ClearLatencyMs()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ResetPingLatencyMs()
|
||||
_u.mutation.SetPingLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetPingLatencyMs(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddPingLatencyMs adds value to the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.AddPingLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) ClearPingLatencyMs() *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ClearPingLatencyMs()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetMessage(v string) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.SetMessage(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableMessage sets the "message" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableMessage(v *string) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetMessage(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearMessage clears the value of the "message" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) ClearMessage() *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ClearMessage()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.SetCheckedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryUpdate {
|
||||
if v != nil {
|
||||
_u.SetCheckedAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryUpdate {
|
||||
return _u.SetMonitorID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
|
||||
func (_u *ChannelMonitorHistoryUpdate) Mutation() *ChannelMonitorHistoryMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
|
||||
func (_u *ChannelMonitorHistoryUpdate) ClearMonitor() *ChannelMonitorHistoryUpdate {
|
||||
_u.mutation.ClearMonitor()
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *ChannelMonitorHistoryUpdate) Save(ctx context.Context) (int, error) {
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorHistoryUpdate) SaveX(ctx context.Context) int {
|
||||
affected, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_u *ChannelMonitorHistoryUpdate) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorHistoryUpdate) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *ChannelMonitorHistoryUpdate) check() error {
|
||||
if v, ok := _u.mutation.Model(); ok {
|
||||
if err := channelmonitorhistory.ModelValidator(v); err != nil {
|
||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Status(); ok {
|
||||
if err := channelmonitorhistory.StatusValidator(v); err != nil {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Message(); ok {
|
||||
if err := channelmonitorhistory.MessageValidator(v); err != nil {
|
||||
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
|
||||
return errors.New(`ent: clearing a required unique edge "ChannelMonitorHistory.monitor"`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *ChannelMonitorHistoryUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.Model(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedLatencyMs(); ok {
|
||||
_spec.AddField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.LatencyMsCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldLatencyMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.PingLatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedPingLatencyMs(); ok {
|
||||
_spec.AddField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.PingLatencyMsCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.Message(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.MessageCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldMessage, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.CheckedAt(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.MonitorCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: channelmonitorhistory.MonitorTable,
|
||||
Columns: []string{channelmonitorhistory.MonitorColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: channelmonitorhistory.MonitorTable,
|
||||
Columns: []string{channelmonitorhistory.MonitorColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.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{channelmonitorhistory.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryUpdateOne is the builder for updating a single ChannelMonitorHistory entity.
|
||||
type ChannelMonitorHistoryUpdateOne struct {
|
||||
config
|
||||
fields []string
|
||||
hooks []Hook
|
||||
mutation *ChannelMonitorHistoryMutation
|
||||
}
|
||||
|
||||
// SetMonitorID sets the "monitor_id" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetMonitorID(v int64) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.SetMonitorID(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableMonitorID sets the "monitor_id" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableMonitorID(v *int64) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetMonitorID(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetModel sets the "model" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetModel(v string) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.SetModel(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableModel sets the "model" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableModel(v *string) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetModel(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetStatus sets the "status" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetStatus(v channelmonitorhistory.Status) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.SetStatus(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableStatus sets the "status" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableStatus(v *channelmonitorhistory.Status) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetStatus(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLatencyMs sets the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ResetLatencyMs()
|
||||
_u.mutation.SetLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableLatencyMs sets the "latency_ms" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableLatencyMs(v *int) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetLatencyMs(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddLatencyMs adds value to the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) AddLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.AddLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearLatencyMs clears the value of the "latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) ClearLatencyMs() *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ClearLatencyMs()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetPingLatencyMs sets the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetPingLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ResetPingLatencyMs()
|
||||
_u.mutation.SetPingLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillablePingLatencyMs sets the "ping_latency_ms" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillablePingLatencyMs(v *int) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetPingLatencyMs(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddPingLatencyMs adds value to the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) AddPingLatencyMs(v int) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.AddPingLatencyMs(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearPingLatencyMs clears the value of the "ping_latency_ms" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) ClearPingLatencyMs() *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ClearPingLatencyMs()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMessage sets the "message" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetMessage(v string) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.SetMessage(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableMessage sets the "message" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableMessage(v *string) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetMessage(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// ClearMessage clears the value of the "message" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) ClearMessage() *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ClearMessage()
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetCheckedAt sets the "checked_at" field.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetCheckedAt(v time.Time) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.SetCheckedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableCheckedAt sets the "checked_at" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetNillableCheckedAt(v *time.Time) *ChannelMonitorHistoryUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetCheckedAt(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetMonitor sets the "monitor" edge to the ChannelMonitor entity.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SetMonitor(v *ChannelMonitor) *ChannelMonitorHistoryUpdateOne {
|
||||
return _u.SetMonitorID(v.ID)
|
||||
}
|
||||
|
||||
// Mutation returns the ChannelMonitorHistoryMutation object of the builder.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) Mutation() *ChannelMonitorHistoryMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// ClearMonitor clears the "monitor" edge to the ChannelMonitor entity.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) ClearMonitor() *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.ClearMonitor()
|
||||
return _u
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the ChannelMonitorHistoryUpdate builder.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) Where(ps ...predicate.ChannelMonitorHistory) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Select allows selecting one or more fields (columns) of the returned entity.
|
||||
// The default is selecting all fields defined in the entity schema.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) Select(field string, fields ...string) *ChannelMonitorHistoryUpdateOne {
|
||||
_u.fields = append([]string{field}, fields...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the updated ChannelMonitorHistory entity.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) Save(ctx context.Context) (*ChannelMonitorHistory, error) {
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) SaveX(ctx context.Context) *ChannelMonitorHistory {
|
||||
node, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Exec executes the query on the entity.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) ExecX(ctx context.Context) {
|
||||
if err := _u.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) check() error {
|
||||
if v, ok := _u.mutation.Model(); ok {
|
||||
if err := channelmonitorhistory.ModelValidator(v); err != nil {
|
||||
return &ValidationError{Name: "model", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.model": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Status(); ok {
|
||||
if err := channelmonitorhistory.StatusValidator(v); err != nil {
|
||||
return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.status": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Message(); ok {
|
||||
if err := channelmonitorhistory.MessageValidator(v); err != nil {
|
||||
return &ValidationError{Name: "message", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitorHistory.message": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _u.mutation.MonitorCleared() && len(_u.mutation.MonitorIDs()) > 0 {
|
||||
return errors.New(`ent: clearing a required unique edge "ChannelMonitorHistory.monitor"`)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *ChannelMonitorHistoryUpdateOne) sqlSave(ctx context.Context) (_node *ChannelMonitorHistory, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(channelmonitorhistory.Table, channelmonitorhistory.Columns, sqlgraph.NewFieldSpec(channelmonitorhistory.FieldID, field.TypeInt64))
|
||||
id, ok := _u.mutation.ID()
|
||||
if !ok {
|
||||
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "ChannelMonitorHistory.id" for update`)}
|
||||
}
|
||||
_spec.Node.ID.Value = id
|
||||
if fields := _u.fields; len(fields) > 0 {
|
||||
_spec.Node.Columns = make([]string, 0, len(fields))
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, channelmonitorhistory.FieldID)
|
||||
for _, f := range fields {
|
||||
if !channelmonitorhistory.ValidColumn(f) {
|
||||
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
if f != channelmonitorhistory.FieldID {
|
||||
_spec.Node.Columns = append(_spec.Node.Columns, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ps := _u.mutation.predicates; len(ps) > 0 {
|
||||
_spec.Predicate = func(selector *sql.Selector) {
|
||||
for i := range ps {
|
||||
ps[i](selector)
|
||||
}
|
||||
}
|
||||
}
|
||||
if value, ok := _u.mutation.Model(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldModel, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Status(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldStatus, field.TypeEnum, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedLatencyMs(); ok {
|
||||
_spec.AddField(channelmonitorhistory.FieldLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.LatencyMsCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldLatencyMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.PingLatencyMs(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedPingLatencyMs(); ok {
|
||||
_spec.AddField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt, value)
|
||||
}
|
||||
if _u.mutation.PingLatencyMsCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldPingLatencyMs, field.TypeInt)
|
||||
}
|
||||
if value, ok := _u.mutation.Message(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldMessage, field.TypeString, value)
|
||||
}
|
||||
if _u.mutation.MessageCleared() {
|
||||
_spec.ClearField(channelmonitorhistory.FieldMessage, field.TypeString)
|
||||
}
|
||||
if value, ok := _u.mutation.CheckedAt(); ok {
|
||||
_spec.SetField(channelmonitorhistory.FieldCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
if _u.mutation.MonitorCleared() {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: channelmonitorhistory.MonitorTable,
|
||||
Columns: []string{channelmonitorhistory.MonitorColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
|
||||
}
|
||||
if nodes := _u.mutation.MonitorIDs(); len(nodes) > 0 {
|
||||
edge := &sqlgraph.EdgeSpec{
|
||||
Rel: sqlgraph.M2O,
|
||||
Inverse: true,
|
||||
Table: channelmonitorhistory.MonitorTable,
|
||||
Columns: []string{channelmonitorhistory.MonitorColumn},
|
||||
Bidi: false,
|
||||
Target: &sqlgraph.EdgeTarget{
|
||||
IDSpec: sqlgraph.NewFieldSpec(channelmonitor.FieldID, field.TypeInt64),
|
||||
},
|
||||
}
|
||||
for _, k := range nodes {
|
||||
edge.Target.Nodes = append(edge.Target.Nodes, k)
|
||||
}
|
||||
_spec.Edges.Add = append(_spec.Edges.Add, edge)
|
||||
}
|
||||
_node = &ChannelMonitorHistory{config: _u.config}
|
||||
_spec.Assign = _node.assignValues
|
||||
_spec.ScanValues = _node.scanValues
|
||||
if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{channelmonitorhistory.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
@@ -68,6 +70,10 @@ type Client struct {
|
||||
AuthIdentity *AuthIdentityClient
|
||||
// AuthIdentityChannel is the client for interacting with the AuthIdentityChannel builders.
|
||||
AuthIdentityChannel *AuthIdentityChannelClient
|
||||
// ChannelMonitor is the client for interacting with the ChannelMonitor builders.
|
||||
ChannelMonitor *ChannelMonitorClient
|
||||
// ChannelMonitorHistory is the client for interacting with the ChannelMonitorHistory builders.
|
||||
ChannelMonitorHistory *ChannelMonitorHistoryClient
|
||||
// ErrorPassthroughRule is the client for interacting with the ErrorPassthroughRule builders.
|
||||
ErrorPassthroughRule *ErrorPassthroughRuleClient
|
||||
// Group is the client for interacting with the Group builders.
|
||||
@@ -132,6 +138,8 @@ func (c *Client) init() {
|
||||
c.AnnouncementRead = NewAnnouncementReadClient(c.config)
|
||||
c.AuthIdentity = NewAuthIdentityClient(c.config)
|
||||
c.AuthIdentityChannel = NewAuthIdentityChannelClient(c.config)
|
||||
c.ChannelMonitor = NewChannelMonitorClient(c.config)
|
||||
c.ChannelMonitorHistory = NewChannelMonitorHistoryClient(c.config)
|
||||
c.ErrorPassthroughRule = NewErrorPassthroughRuleClient(c.config)
|
||||
c.Group = NewGroupClient(c.config)
|
||||
c.IdempotencyRecord = NewIdempotencyRecordClient(c.config)
|
||||
@@ -254,6 +262,8 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||
AuthIdentity: NewAuthIdentityClient(cfg),
|
||||
AuthIdentityChannel: NewAuthIdentityChannelClient(cfg),
|
||||
ChannelMonitor: NewChannelMonitorClient(cfg),
|
||||
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
|
||||
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
|
||||
Group: NewGroupClient(cfg),
|
||||
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
|
||||
@@ -303,6 +313,8 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||
AnnouncementRead: NewAnnouncementReadClient(cfg),
|
||||
AuthIdentity: NewAuthIdentityClient(cfg),
|
||||
AuthIdentityChannel: NewAuthIdentityChannelClient(cfg),
|
||||
ChannelMonitor: NewChannelMonitorClient(cfg),
|
||||
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
|
||||
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
|
||||
Group: NewGroupClient(cfg),
|
||||
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
|
||||
@@ -356,12 +368,13 @@ func (c *Client) Close() error {
|
||||
func (c *Client) Use(hooks ...Hook) {
|
||||
for _, n := range []interface{ Use(...Hook) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.AuthIdentity, c.AuthIdentityChannel, c.ErrorPassthroughRule, c.Group,
|
||||
c.IdempotencyRecord, c.IdentityAdoptionDecision, c.PaymentAuditLog,
|
||||
c.PaymentOrder, c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode,
|
||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||
c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.AuthIdentity, c.AuthIdentityChannel, c.ChannelMonitor,
|
||||
c.ChannelMonitorHistory, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
|
||||
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
|
||||
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
|
||||
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
|
||||
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
} {
|
||||
n.Use(hooks...)
|
||||
@@ -373,12 +386,13 @@ func (c *Client) Use(hooks ...Hook) {
|
||||
func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||
for _, n := range []interface{ Intercept(...Interceptor) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.AuthIdentity, c.AuthIdentityChannel, c.ErrorPassthroughRule, c.Group,
|
||||
c.IdempotencyRecord, c.IdentityAdoptionDecision, c.PaymentAuditLog,
|
||||
c.PaymentOrder, c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode,
|
||||
c.PromoCodeUsage, c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting,
|
||||
c.SubscriptionPlan, c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.AuthIdentity, c.AuthIdentityChannel, c.ChannelMonitor,
|
||||
c.ChannelMonitorHistory, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
|
||||
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
|
||||
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
|
||||
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
|
||||
c.TLSFingerprintProfile, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
} {
|
||||
n.Intercept(interceptors...)
|
||||
@@ -402,6 +416,10 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return c.AuthIdentity.mutate(ctx, m)
|
||||
case *AuthIdentityChannelMutation:
|
||||
return c.AuthIdentityChannel.mutate(ctx, m)
|
||||
case *ChannelMonitorMutation:
|
||||
return c.ChannelMonitor.mutate(ctx, m)
|
||||
case *ChannelMonitorHistoryMutation:
|
||||
return c.ChannelMonitorHistory.mutate(ctx, m)
|
||||
case *ErrorPassthroughRuleMutation:
|
||||
return c.ErrorPassthroughRule.mutate(ctx, m)
|
||||
case *GroupMutation:
|
||||
@@ -1595,6 +1613,304 @@ func (c *AuthIdentityChannelClient) mutate(ctx context.Context, m *AuthIdentityC
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelMonitorClient is a client for the ChannelMonitor schema.
|
||||
type ChannelMonitorClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewChannelMonitorClient returns a client for the ChannelMonitor from the given config.
|
||||
func NewChannelMonitorClient(c config) *ChannelMonitorClient {
|
||||
return &ChannelMonitorClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `channelmonitor.Hooks(f(g(h())))`.
|
||||
func (c *ChannelMonitorClient) Use(hooks ...Hook) {
|
||||
c.hooks.ChannelMonitor = append(c.hooks.ChannelMonitor, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `channelmonitor.Intercept(f(g(h())))`.
|
||||
func (c *ChannelMonitorClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.ChannelMonitor = append(c.inters.ChannelMonitor, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a ChannelMonitor entity.
|
||||
func (c *ChannelMonitorClient) Create() *ChannelMonitorCreate {
|
||||
mutation := newChannelMonitorMutation(c.config, OpCreate)
|
||||
return &ChannelMonitorCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of ChannelMonitor entities.
|
||||
func (c *ChannelMonitorClient) CreateBulk(builders ...*ChannelMonitorCreate) *ChannelMonitorCreateBulk {
|
||||
return &ChannelMonitorCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||
// a builder and applies setFunc on it.
|
||||
func (c *ChannelMonitorClient) MapCreateBulk(slice any, setFunc func(*ChannelMonitorCreate, int)) *ChannelMonitorCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &ChannelMonitorCreateBulk{err: fmt.Errorf("calling to ChannelMonitorClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*ChannelMonitorCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &ChannelMonitorCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for ChannelMonitor.
|
||||
func (c *ChannelMonitorClient) Update() *ChannelMonitorUpdate {
|
||||
mutation := newChannelMonitorMutation(c.config, OpUpdate)
|
||||
return &ChannelMonitorUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *ChannelMonitorClient) UpdateOne(_m *ChannelMonitor) *ChannelMonitorUpdateOne {
|
||||
mutation := newChannelMonitorMutation(c.config, OpUpdateOne, withChannelMonitor(_m))
|
||||
return &ChannelMonitorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *ChannelMonitorClient) UpdateOneID(id int64) *ChannelMonitorUpdateOne {
|
||||
mutation := newChannelMonitorMutation(c.config, OpUpdateOne, withChannelMonitorID(id))
|
||||
return &ChannelMonitorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for ChannelMonitor.
|
||||
func (c *ChannelMonitorClient) Delete() *ChannelMonitorDelete {
|
||||
mutation := newChannelMonitorMutation(c.config, OpDelete)
|
||||
return &ChannelMonitorDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *ChannelMonitorClient) DeleteOne(_m *ChannelMonitor) *ChannelMonitorDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *ChannelMonitorClient) DeleteOneID(id int64) *ChannelMonitorDeleteOne {
|
||||
builder := c.Delete().Where(channelmonitor.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &ChannelMonitorDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for ChannelMonitor.
|
||||
func (c *ChannelMonitorClient) Query() *ChannelMonitorQuery {
|
||||
return &ChannelMonitorQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeChannelMonitor},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a ChannelMonitor entity by its id.
|
||||
func (c *ChannelMonitorClient) Get(ctx context.Context, id int64) (*ChannelMonitor, error) {
|
||||
return c.Query().Where(channelmonitor.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *ChannelMonitorClient) GetX(ctx context.Context, id int64) *ChannelMonitor {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// QueryHistory queries the history edge of a ChannelMonitor.
|
||||
func (c *ChannelMonitorClient) QueryHistory(_m *ChannelMonitor) *ChannelMonitorHistoryQuery {
|
||||
query := (&ChannelMonitorHistoryClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(channelmonitor.Table, channelmonitor.FieldID, id),
|
||||
sqlgraph.To(channelmonitorhistory.Table, channelmonitorhistory.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.O2M, false, channelmonitor.HistoryTable, channelmonitor.HistoryColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *ChannelMonitorClient) Hooks() []Hook {
|
||||
return c.hooks.ChannelMonitor
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *ChannelMonitorClient) Interceptors() []Interceptor {
|
||||
return c.inters.ChannelMonitor
|
||||
}
|
||||
|
||||
func (c *ChannelMonitorClient) mutate(ctx context.Context, m *ChannelMonitorMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&ChannelMonitorCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&ChannelMonitorUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&ChannelMonitorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&ChannelMonitorDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown ChannelMonitor mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryClient is a client for the ChannelMonitorHistory schema.
|
||||
type ChannelMonitorHistoryClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewChannelMonitorHistoryClient returns a client for the ChannelMonitorHistory from the given config.
|
||||
func NewChannelMonitorHistoryClient(c config) *ChannelMonitorHistoryClient {
|
||||
return &ChannelMonitorHistoryClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `channelmonitorhistory.Hooks(f(g(h())))`.
|
||||
func (c *ChannelMonitorHistoryClient) Use(hooks ...Hook) {
|
||||
c.hooks.ChannelMonitorHistory = append(c.hooks.ChannelMonitorHistory, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `channelmonitorhistory.Intercept(f(g(h())))`.
|
||||
func (c *ChannelMonitorHistoryClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.ChannelMonitorHistory = append(c.inters.ChannelMonitorHistory, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a ChannelMonitorHistory entity.
|
||||
func (c *ChannelMonitorHistoryClient) Create() *ChannelMonitorHistoryCreate {
|
||||
mutation := newChannelMonitorHistoryMutation(c.config, OpCreate)
|
||||
return &ChannelMonitorHistoryCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of ChannelMonitorHistory entities.
|
||||
func (c *ChannelMonitorHistoryClient) CreateBulk(builders ...*ChannelMonitorHistoryCreate) *ChannelMonitorHistoryCreateBulk {
|
||||
return &ChannelMonitorHistoryCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates
|
||||
// a builder and applies setFunc on it.
|
||||
func (c *ChannelMonitorHistoryClient) MapCreateBulk(slice any, setFunc func(*ChannelMonitorHistoryCreate, int)) *ChannelMonitorHistoryCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &ChannelMonitorHistoryCreateBulk{err: fmt.Errorf("calling to ChannelMonitorHistoryClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*ChannelMonitorHistoryCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &ChannelMonitorHistoryCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for ChannelMonitorHistory.
|
||||
func (c *ChannelMonitorHistoryClient) Update() *ChannelMonitorHistoryUpdate {
|
||||
mutation := newChannelMonitorHistoryMutation(c.config, OpUpdate)
|
||||
return &ChannelMonitorHistoryUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *ChannelMonitorHistoryClient) UpdateOne(_m *ChannelMonitorHistory) *ChannelMonitorHistoryUpdateOne {
|
||||
mutation := newChannelMonitorHistoryMutation(c.config, OpUpdateOne, withChannelMonitorHistory(_m))
|
||||
return &ChannelMonitorHistoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *ChannelMonitorHistoryClient) UpdateOneID(id int64) *ChannelMonitorHistoryUpdateOne {
|
||||
mutation := newChannelMonitorHistoryMutation(c.config, OpUpdateOne, withChannelMonitorHistoryID(id))
|
||||
return &ChannelMonitorHistoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for ChannelMonitorHistory.
|
||||
func (c *ChannelMonitorHistoryClient) Delete() *ChannelMonitorHistoryDelete {
|
||||
mutation := newChannelMonitorHistoryMutation(c.config, OpDelete)
|
||||
return &ChannelMonitorHistoryDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *ChannelMonitorHistoryClient) DeleteOne(_m *ChannelMonitorHistory) *ChannelMonitorHistoryDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *ChannelMonitorHistoryClient) DeleteOneID(id int64) *ChannelMonitorHistoryDeleteOne {
|
||||
builder := c.Delete().Where(channelmonitorhistory.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &ChannelMonitorHistoryDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for ChannelMonitorHistory.
|
||||
func (c *ChannelMonitorHistoryClient) Query() *ChannelMonitorHistoryQuery {
|
||||
return &ChannelMonitorHistoryQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeChannelMonitorHistory},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a ChannelMonitorHistory entity by its id.
|
||||
func (c *ChannelMonitorHistoryClient) Get(ctx context.Context, id int64) (*ChannelMonitorHistory, error) {
|
||||
return c.Query().Where(channelmonitorhistory.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *ChannelMonitorHistoryClient) GetX(ctx context.Context, id int64) *ChannelMonitorHistory {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// QueryMonitor queries the monitor edge of a ChannelMonitorHistory.
|
||||
func (c *ChannelMonitorHistoryClient) QueryMonitor(_m *ChannelMonitorHistory) *ChannelMonitorQuery {
|
||||
query := (&ChannelMonitorClient{config: c.config}).Query()
|
||||
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
|
||||
id := _m.ID
|
||||
step := sqlgraph.NewStep(
|
||||
sqlgraph.From(channelmonitorhistory.Table, channelmonitorhistory.FieldID, id),
|
||||
sqlgraph.To(channelmonitor.Table, channelmonitor.FieldID),
|
||||
sqlgraph.Edge(sqlgraph.M2O, true, channelmonitorhistory.MonitorTable, channelmonitorhistory.MonitorColumn),
|
||||
)
|
||||
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
|
||||
return fromV, nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *ChannelMonitorHistoryClient) Hooks() []Hook {
|
||||
return c.hooks.ChannelMonitorHistory
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *ChannelMonitorHistoryClient) Interceptors() []Interceptor {
|
||||
return c.inters.ChannelMonitorHistory
|
||||
}
|
||||
|
||||
func (c *ChannelMonitorHistoryClient) mutate(ctx context.Context, m *ChannelMonitorHistoryMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&ChannelMonitorHistoryCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&ChannelMonitorHistoryUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&ChannelMonitorHistoryUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&ChannelMonitorHistoryDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown ChannelMonitorHistory mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorPassthroughRuleClient is a client for the ErrorPassthroughRule schema.
|
||||
type ErrorPassthroughRuleClient struct {
|
||||
config
|
||||
@@ -5355,21 +5671,23 @@ func (c *UserSubscriptionClient) mutate(ctx context.Context, m *UserSubscription
|
||||
type (
|
||||
hooks struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
|
||||
AuthIdentityChannel, ErrorPassthroughRule, Group, IdempotencyRecord,
|
||||
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
|
||||
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
|
||||
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
|
||||
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
||||
UserAttributeValue, UserSubscription []ent.Hook
|
||||
AuthIdentityChannel, ChannelMonitor, ChannelMonitorHistory,
|
||||
ErrorPassthroughRule, Group, IdempotencyRecord, IdentityAdoptionDecision,
|
||||
PaymentAuditLog, PaymentOrder, PaymentProviderInstance, PendingAuthSession,
|
||||
PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting,
|
||||
SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User,
|
||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||
UserSubscription []ent.Hook
|
||||
}
|
||||
inters struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
|
||||
AuthIdentityChannel, ErrorPassthroughRule, Group, IdempotencyRecord,
|
||||
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
|
||||
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
|
||||
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
|
||||
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
|
||||
UserAttributeValue, UserSubscription []ent.Interceptor
|
||||
AuthIdentityChannel, ChannelMonitor, ChannelMonitorHistory,
|
||||
ErrorPassthroughRule, Group, IdempotencyRecord, IdentityAdoptionDecision,
|
||||
PaymentAuditLog, PaymentOrder, PaymentProviderInstance, PendingAuthSession,
|
||||
PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting,
|
||||
SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User,
|
||||
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
|
||||
UserSubscription []ent.Interceptor
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
@@ -109,6 +111,8 @@ func checkColumn(t, c string) error {
|
||||
announcementread.Table: announcementread.ValidColumn,
|
||||
authidentity.Table: authidentity.ValidColumn,
|
||||
authidentitychannel.Table: authidentitychannel.ValidColumn,
|
||||
channelmonitor.Table: channelmonitor.ValidColumn,
|
||||
channelmonitorhistory.Table: channelmonitorhistory.ValidColumn,
|
||||
errorpassthroughrule.Table: errorpassthroughrule.ValidColumn,
|
||||
group.Table: group.ValidColumn,
|
||||
idempotencyrecord.Table: idempotencyrecord.ValidColumn,
|
||||
|
||||
@@ -93,6 +93,30 @@ func (f AuthIdentityChannelFunc) Mutate(ctx context.Context, m ent.Mutation) (en
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.AuthIdentityChannelMutation", m)
|
||||
}
|
||||
|
||||
// The ChannelMonitorFunc type is an adapter to allow the use of ordinary
|
||||
// function as ChannelMonitor mutator.
|
||||
type ChannelMonitorFunc func(context.Context, *ent.ChannelMonitorMutation) (ent.Value, error)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f ChannelMonitorFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.ChannelMonitorMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ChannelMonitorMutation", m)
|
||||
}
|
||||
|
||||
// The ChannelMonitorHistoryFunc type is an adapter to allow the use of ordinary
|
||||
// function as ChannelMonitorHistory mutator.
|
||||
type ChannelMonitorHistoryFunc func(context.Context, *ent.ChannelMonitorHistoryMutation) (ent.Value, error)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f ChannelMonitorHistoryFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.ChannelMonitorHistoryMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ChannelMonitorHistoryMutation", m)
|
||||
}
|
||||
|
||||
// The ErrorPassthroughRuleFunc type is an adapter to allow the use of ordinary
|
||||
// function as ErrorPassthroughRule mutator.
|
||||
type ErrorPassthroughRuleFunc func(context.Context, *ent.ErrorPassthroughRuleMutation) (ent.Value, error)
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
@@ -286,6 +288,60 @@ func (f TraverseAuthIdentityChannel) Traverse(ctx context.Context, q ent.Query)
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.AuthIdentityChannelQuery", q)
|
||||
}
|
||||
|
||||
// The ChannelMonitorFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type ChannelMonitorFunc func(context.Context, *ent.ChannelMonitorQuery) (ent.Value, error)
|
||||
|
||||
// Query calls f(ctx, q).
|
||||
func (f ChannelMonitorFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||
if q, ok := q.(*ent.ChannelMonitorQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected query type %T. expect *ent.ChannelMonitorQuery", q)
|
||||
}
|
||||
|
||||
// The TraverseChannelMonitor type is an adapter to allow the use of ordinary function as Traverser.
|
||||
type TraverseChannelMonitor func(context.Context, *ent.ChannelMonitorQuery) error
|
||||
|
||||
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||
func (f TraverseChannelMonitor) Intercept(next ent.Querier) ent.Querier {
|
||||
return next
|
||||
}
|
||||
|
||||
// Traverse calls f(ctx, q).
|
||||
func (f TraverseChannelMonitor) Traverse(ctx context.Context, q ent.Query) error {
|
||||
if q, ok := q.(*ent.ChannelMonitorQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.ChannelMonitorQuery", q)
|
||||
}
|
||||
|
||||
// The ChannelMonitorHistoryFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type ChannelMonitorHistoryFunc func(context.Context, *ent.ChannelMonitorHistoryQuery) (ent.Value, error)
|
||||
|
||||
// Query calls f(ctx, q).
|
||||
func (f ChannelMonitorHistoryFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||
if q, ok := q.(*ent.ChannelMonitorHistoryQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected query type %T. expect *ent.ChannelMonitorHistoryQuery", q)
|
||||
}
|
||||
|
||||
// The TraverseChannelMonitorHistory type is an adapter to allow the use of ordinary function as Traverser.
|
||||
type TraverseChannelMonitorHistory func(context.Context, *ent.ChannelMonitorHistoryQuery) error
|
||||
|
||||
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||
func (f TraverseChannelMonitorHistory) Intercept(next ent.Querier) ent.Querier {
|
||||
return next
|
||||
}
|
||||
|
||||
// Traverse calls f(ctx, q).
|
||||
func (f TraverseChannelMonitorHistory) Traverse(ctx context.Context, q ent.Query) error {
|
||||
if q, ok := q.(*ent.ChannelMonitorHistoryQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.ChannelMonitorHistoryQuery", q)
|
||||
}
|
||||
|
||||
// The ErrorPassthroughRuleFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type ErrorPassthroughRuleFunc func(context.Context, *ent.ErrorPassthroughRuleQuery) (ent.Value, error)
|
||||
|
||||
@@ -924,6 +980,10 @@ func NewQuery(q ent.Query) (Query, error) {
|
||||
return &query[*ent.AuthIdentityQuery, predicate.AuthIdentity, authidentity.OrderOption]{typ: ent.TypeAuthIdentity, tq: q}, nil
|
||||
case *ent.AuthIdentityChannelQuery:
|
||||
return &query[*ent.AuthIdentityChannelQuery, predicate.AuthIdentityChannel, authidentitychannel.OrderOption]{typ: ent.TypeAuthIdentityChannel, tq: q}, nil
|
||||
case *ent.ChannelMonitorQuery:
|
||||
return &query[*ent.ChannelMonitorQuery, predicate.ChannelMonitor, channelmonitor.OrderOption]{typ: ent.TypeChannelMonitor, tq: q}, nil
|
||||
case *ent.ChannelMonitorHistoryQuery:
|
||||
return &query[*ent.ChannelMonitorHistoryQuery, predicate.ChannelMonitorHistory, channelmonitorhistory.OrderOption]{typ: ent.TypeChannelMonitorHistory, tq: q}, nil
|
||||
case *ent.ErrorPassthroughRuleQuery:
|
||||
return &query[*ent.ErrorPassthroughRuleQuery, predicate.ErrorPassthroughRule, errorpassthroughrule.OrderOption]{typ: ent.TypeErrorPassthroughRule, tq: q}, nil
|
||||
case *ent.GroupQuery:
|
||||
|
||||
@@ -421,6 +421,83 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// ChannelMonitorsColumns holds the columns for the "channel_monitors" table.
|
||||
ChannelMonitorsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "name", Type: field.TypeString, Size: 100},
|
||||
{Name: "provider", Type: field.TypeEnum, Enums: []string{"openai", "anthropic", "gemini"}},
|
||||
{Name: "endpoint", Type: field.TypeString, Size: 500},
|
||||
{Name: "api_key_encrypted", Type: field.TypeString},
|
||||
{Name: "primary_model", Type: field.TypeString, Size: 200},
|
||||
{Name: "extra_models", Type: field.TypeJSON},
|
||||
{Name: "group_name", Type: field.TypeString, Nullable: true, Size: 100, Default: ""},
|
||||
{Name: "enabled", Type: field.TypeBool, Default: true},
|
||||
{Name: "interval_seconds", Type: field.TypeInt},
|
||||
{Name: "last_checked_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "created_by", Type: field.TypeInt64},
|
||||
}
|
||||
// ChannelMonitorsTable holds the schema information for the "channel_monitors" table.
|
||||
ChannelMonitorsTable = &schema.Table{
|
||||
Name: "channel_monitors",
|
||||
Columns: ChannelMonitorsColumns,
|
||||
PrimaryKey: []*schema.Column{ChannelMonitorsColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "channelmonitor_enabled_last_checked_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[10], ChannelMonitorsColumns[12]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_provider",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[4]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_group_name",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[9]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// ChannelMonitorHistoriesColumns holds the columns for the "channel_monitor_histories" table.
|
||||
ChannelMonitorHistoriesColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "model", Type: field.TypeString, Size: 200},
|
||||
{Name: "status", Type: field.TypeEnum, Enums: []string{"operational", "degraded", "failed", "error"}},
|
||||
{Name: "latency_ms", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "ping_latency_ms", Type: field.TypeInt, Nullable: true},
|
||||
{Name: "message", Type: field.TypeString, Nullable: true, Size: 500, Default: ""},
|
||||
{Name: "checked_at", Type: field.TypeTime},
|
||||
{Name: "monitor_id", Type: field.TypeInt64},
|
||||
}
|
||||
// ChannelMonitorHistoriesTable holds the schema information for the "channel_monitor_histories" table.
|
||||
ChannelMonitorHistoriesTable = &schema.Table{
|
||||
Name: "channel_monitor_histories",
|
||||
Columns: ChannelMonitorHistoriesColumns,
|
||||
PrimaryKey: []*schema.Column{ChannelMonitorHistoriesColumns[0]},
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "channel_monitor_histories_channel_monitors_history",
|
||||
Columns: []*schema.Column{ChannelMonitorHistoriesColumns[7]},
|
||||
RefColumns: []*schema.Column{ChannelMonitorsColumns[0]},
|
||||
OnDelete: schema.Cascade,
|
||||
},
|
||||
},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "channelmonitorhistory_monitor_id_model_checked_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorHistoriesColumns[7], ChannelMonitorHistoriesColumns[1], ChannelMonitorHistoriesColumns[6]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitorhistory_checked_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorHistoriesColumns[6]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// ErrorPassthroughRulesColumns holds the columns for the "error_passthrough_rules" table.
|
||||
ErrorPassthroughRulesColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
@@ -1276,7 +1353,7 @@ var (
|
||||
{Name: "totp_secret_encrypted", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "totp_enabled", Type: field.TypeBool, Default: false},
|
||||
{Name: "totp_enabled_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "signup_source", Type: field.TypeString, Size: 20, Default: "email"},
|
||||
{Name: "signup_source", Type: field.TypeString, Default: "email"},
|
||||
{Name: "last_login_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "last_active_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "balance_notify_enabled", Type: field.TypeBool, Default: true},
|
||||
@@ -1520,6 +1597,8 @@ var (
|
||||
AnnouncementReadsTable,
|
||||
AuthIdentitiesTable,
|
||||
AuthIdentityChannelsTable,
|
||||
ChannelMonitorsTable,
|
||||
ChannelMonitorHistoriesTable,
|
||||
ErrorPassthroughRulesTable,
|
||||
GroupsTable,
|
||||
IdempotencyRecordsTable,
|
||||
@@ -1577,6 +1656,13 @@ func init() {
|
||||
AuthIdentityChannelsTable.Annotation = &entsql.Annotation{
|
||||
Table: "auth_identity_channels",
|
||||
}
|
||||
ChannelMonitorsTable.Annotation = &entsql.Annotation{
|
||||
Table: "channel_monitors",
|
||||
}
|
||||
ChannelMonitorHistoriesTable.ForeignKeys[0].RefTable = ChannelMonitorsTable
|
||||
ChannelMonitorHistoriesTable.Annotation = &entsql.Annotation{
|
||||
Table: "channel_monitor_histories",
|
||||
}
|
||||
ErrorPassthroughRulesTable.Annotation = &entsql.Annotation{
|
||||
Table: "error_passthrough_rules",
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,12 @@ type AuthIdentity func(*sql.Selector)
|
||||
// AuthIdentityChannel is the predicate function for authidentitychannel builders.
|
||||
type AuthIdentityChannel func(*sql.Selector)
|
||||
|
||||
// ChannelMonitor is the predicate function for channelmonitor builders.
|
||||
type ChannelMonitor func(*sql.Selector)
|
||||
|
||||
// ChannelMonitorHistory is the predicate function for channelmonitorhistory builders.
|
||||
type ChannelMonitorHistory func(*sql.Selector)
|
||||
|
||||
// ErrorPassthroughRule is the predicate function for errorpassthroughrule builders.
|
||||
type ErrorPassthroughRule func(*sql.Selector)
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/apikey"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentity"
|
||||
"github.com/Wei-Shaw/sub2api/ent/authidentitychannel"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
|
||||
"github.com/Wei-Shaw/sub2api/ent/group"
|
||||
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
|
||||
@@ -427,6 +429,127 @@ func init() {
|
||||
authidentitychannelDescMetadata := authidentitychannelFields[6].Descriptor()
|
||||
// authidentitychannel.DefaultMetadata holds the default value on creation for the metadata field.
|
||||
authidentitychannel.DefaultMetadata = authidentitychannelDescMetadata.Default.(func() map[string]interface{})
|
||||
channelmonitorMixin := schema.ChannelMonitor{}.Mixin()
|
||||
channelmonitorMixinFields0 := channelmonitorMixin[0].Fields()
|
||||
_ = channelmonitorMixinFields0
|
||||
channelmonitorFields := schema.ChannelMonitor{}.Fields()
|
||||
_ = channelmonitorFields
|
||||
// channelmonitorDescCreatedAt is the schema descriptor for created_at field.
|
||||
channelmonitorDescCreatedAt := channelmonitorMixinFields0[0].Descriptor()
|
||||
// channelmonitor.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
channelmonitor.DefaultCreatedAt = channelmonitorDescCreatedAt.Default.(func() time.Time)
|
||||
// channelmonitorDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
channelmonitorDescUpdatedAt := channelmonitorMixinFields0[1].Descriptor()
|
||||
// channelmonitor.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
channelmonitor.DefaultUpdatedAt = channelmonitorDescUpdatedAt.Default.(func() time.Time)
|
||||
// channelmonitor.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
channelmonitor.UpdateDefaultUpdatedAt = channelmonitorDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||
// channelmonitorDescName is the schema descriptor for name field.
|
||||
channelmonitorDescName := channelmonitorFields[0].Descriptor()
|
||||
// channelmonitor.NameValidator is a validator for the "name" field. It is called by the builders before save.
|
||||
channelmonitor.NameValidator = func() func(string) error {
|
||||
validators := channelmonitorDescName.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(name string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescEndpoint is the schema descriptor for endpoint field.
|
||||
channelmonitorDescEndpoint := channelmonitorFields[2].Descriptor()
|
||||
// channelmonitor.EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
|
||||
channelmonitor.EndpointValidator = func() func(string) error {
|
||||
validators := channelmonitorDescEndpoint.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(endpoint string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescAPIKeyEncrypted is the schema descriptor for api_key_encrypted field.
|
||||
channelmonitorDescAPIKeyEncrypted := channelmonitorFields[3].Descriptor()
|
||||
// channelmonitor.APIKeyEncryptedValidator is a validator for the "api_key_encrypted" field. It is called by the builders before save.
|
||||
channelmonitor.APIKeyEncryptedValidator = channelmonitorDescAPIKeyEncrypted.Validators[0].(func(string) error)
|
||||
// channelmonitorDescPrimaryModel is the schema descriptor for primary_model field.
|
||||
channelmonitorDescPrimaryModel := channelmonitorFields[4].Descriptor()
|
||||
// channelmonitor.PrimaryModelValidator is a validator for the "primary_model" field. It is called by the builders before save.
|
||||
channelmonitor.PrimaryModelValidator = func() func(string) error {
|
||||
validators := channelmonitorDescPrimaryModel.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(primary_model string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(primary_model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorDescExtraModels is the schema descriptor for extra_models field.
|
||||
channelmonitorDescExtraModels := channelmonitorFields[5].Descriptor()
|
||||
// channelmonitor.DefaultExtraModels holds the default value on creation for the extra_models field.
|
||||
channelmonitor.DefaultExtraModels = channelmonitorDescExtraModels.Default.([]string)
|
||||
// channelmonitorDescGroupName is the schema descriptor for group_name field.
|
||||
channelmonitorDescGroupName := channelmonitorFields[6].Descriptor()
|
||||
// channelmonitor.DefaultGroupName holds the default value on creation for the group_name field.
|
||||
channelmonitor.DefaultGroupName = channelmonitorDescGroupName.Default.(string)
|
||||
// channelmonitor.GroupNameValidator is a validator for the "group_name" field. It is called by the builders before save.
|
||||
channelmonitor.GroupNameValidator = channelmonitorDescGroupName.Validators[0].(func(string) error)
|
||||
// channelmonitorDescEnabled is the schema descriptor for enabled field.
|
||||
channelmonitorDescEnabled := channelmonitorFields[7].Descriptor()
|
||||
// channelmonitor.DefaultEnabled holds the default value on creation for the enabled field.
|
||||
channelmonitor.DefaultEnabled = channelmonitorDescEnabled.Default.(bool)
|
||||
// channelmonitorDescIntervalSeconds is the schema descriptor for interval_seconds field.
|
||||
channelmonitorDescIntervalSeconds := channelmonitorFields[8].Descriptor()
|
||||
// channelmonitor.IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
|
||||
channelmonitor.IntervalSecondsValidator = channelmonitorDescIntervalSeconds.Validators[0].(func(int) error)
|
||||
channelmonitorhistoryFields := schema.ChannelMonitorHistory{}.Fields()
|
||||
_ = channelmonitorhistoryFields
|
||||
// channelmonitorhistoryDescModel is the schema descriptor for model field.
|
||||
channelmonitorhistoryDescModel := channelmonitorhistoryFields[1].Descriptor()
|
||||
// channelmonitorhistory.ModelValidator is a validator for the "model" field. It is called by the builders before save.
|
||||
channelmonitorhistory.ModelValidator = func() func(string) error {
|
||||
validators := channelmonitorhistoryDescModel.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(model string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// channelmonitorhistoryDescMessage is the schema descriptor for message field.
|
||||
channelmonitorhistoryDescMessage := channelmonitorhistoryFields[5].Descriptor()
|
||||
// channelmonitorhistory.DefaultMessage holds the default value on creation for the message field.
|
||||
channelmonitorhistory.DefaultMessage = channelmonitorhistoryDescMessage.Default.(string)
|
||||
// channelmonitorhistory.MessageValidator is a validator for the "message" field. It is called by the builders before save.
|
||||
channelmonitorhistory.MessageValidator = channelmonitorhistoryDescMessage.Validators[0].(func(string) error)
|
||||
// channelmonitorhistoryDescCheckedAt is the schema descriptor for checked_at field.
|
||||
channelmonitorhistoryDescCheckedAt := channelmonitorhistoryFields[6].Descriptor()
|
||||
// channelmonitorhistory.DefaultCheckedAt holds the default value on creation for the checked_at field.
|
||||
channelmonitorhistory.DefaultCheckedAt = channelmonitorhistoryDescCheckedAt.Default.(func() time.Time)
|
||||
errorpassthroughruleMixin := schema.ErrorPassthroughRule{}.Mixin()
|
||||
errorpassthroughruleMixinFields0 := errorpassthroughruleMixin[0].Fields()
|
||||
_ = errorpassthroughruleMixinFields0
|
||||
|
||||
81
backend/ent/schema/channel_monitor.go
Normal file
81
backend/ent/schema/channel_monitor.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
)
|
||||
|
||||
// ChannelMonitor holds the schema definition for the ChannelMonitor entity.
|
||||
// 渠道监控配置:定期对指定 provider/endpoint/api_key 下的模型做心跳测试。
|
||||
type ChannelMonitor struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (ChannelMonitor) Annotations() []schema.Annotation {
|
||||
return []schema.Annotation{
|
||||
entsql.Annotation{Table: "channel_monitors"},
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitor) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.TimeMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitor) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("name").
|
||||
NotEmpty().
|
||||
MaxLen(100),
|
||||
field.Enum("provider").
|
||||
Values("openai", "anthropic", "gemini"),
|
||||
field.String("endpoint").
|
||||
NotEmpty().
|
||||
MaxLen(500).
|
||||
Comment("Provider base origin, e.g. https://api.openai.com"),
|
||||
field.String("api_key_encrypted").
|
||||
NotEmpty().
|
||||
Sensitive().
|
||||
Comment("AES-256-GCM encrypted API key"),
|
||||
field.String("primary_model").
|
||||
NotEmpty().
|
||||
MaxLen(200),
|
||||
field.JSON("extra_models", []string{}).
|
||||
Default([]string{}).
|
||||
Comment("Additional model names to test alongside primary_model"),
|
||||
field.String("group_name").
|
||||
Optional().
|
||||
Default("").
|
||||
MaxLen(100),
|
||||
field.Bool("enabled").
|
||||
Default(true),
|
||||
field.Int("interval_seconds").
|
||||
Range(15, 3600),
|
||||
field.Time("last_checked_at").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.Int64("created_by"),
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitor) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.To("history", ChannelMonitorHistory.Type).
|
||||
Annotations(entsql.OnDelete(entsql.Cascade)),
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitor) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("enabled", "last_checked_at"),
|
||||
index.Fields("provider"),
|
||||
index.Fields("group_name"),
|
||||
}
|
||||
}
|
||||
64
backend/ent/schema/channel_monitor_history.go
Normal file
64
backend/ent/schema/channel_monitor_history.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
)
|
||||
|
||||
// ChannelMonitorHistory holds the schema definition for the ChannelMonitorHistory entity.
|
||||
// 渠道监控历史:每次检测每个模型一行记录,由调度器写入,定期清理 30 天前的旧数据。
|
||||
type ChannelMonitorHistory struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (ChannelMonitorHistory) Annotations() []schema.Annotation {
|
||||
return []schema.Annotation{
|
||||
entsql.Annotation{Table: "channel_monitor_histories"},
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitorHistory) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.Int64("monitor_id"),
|
||||
field.String("model").
|
||||
NotEmpty().
|
||||
MaxLen(200),
|
||||
field.Enum("status").
|
||||
Values("operational", "degraded", "failed", "error"),
|
||||
field.Int("latency_ms").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.Int("ping_latency_ms").
|
||||
Optional().
|
||||
Nillable(),
|
||||
field.String("message").
|
||||
Optional().
|
||||
Default("").
|
||||
MaxLen(500),
|
||||
field.Time("checked_at").
|
||||
Default(time.Now),
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitorHistory) Edges() []ent.Edge {
|
||||
return []ent.Edge{
|
||||
edge.From("monitor", ChannelMonitor.Type).
|
||||
Ref("history").
|
||||
Field("monitor_id").
|
||||
Unique().
|
||||
Required(),
|
||||
}
|
||||
}
|
||||
|
||||
func (ChannelMonitorHistory) Indexes() []ent.Index {
|
||||
return []ent.Index{
|
||||
index.Fields("monitor_id", "model", "checked_at"),
|
||||
index.Fields("checked_at"),
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,10 @@ type Tx struct {
|
||||
AuthIdentity *AuthIdentityClient
|
||||
// AuthIdentityChannel is the client for interacting with the AuthIdentityChannel builders.
|
||||
AuthIdentityChannel *AuthIdentityChannelClient
|
||||
// ChannelMonitor is the client for interacting with the ChannelMonitor builders.
|
||||
ChannelMonitor *ChannelMonitorClient
|
||||
// ChannelMonitorHistory is the client for interacting with the ChannelMonitorHistory builders.
|
||||
ChannelMonitorHistory *ChannelMonitorHistoryClient
|
||||
// ErrorPassthroughRule is the client for interacting with the ErrorPassthroughRule builders.
|
||||
ErrorPassthroughRule *ErrorPassthroughRuleClient
|
||||
// Group is the client for interacting with the Group builders.
|
||||
@@ -212,6 +216,8 @@ func (tx *Tx) init() {
|
||||
tx.AnnouncementRead = NewAnnouncementReadClient(tx.config)
|
||||
tx.AuthIdentity = NewAuthIdentityClient(tx.config)
|
||||
tx.AuthIdentityChannel = NewAuthIdentityChannelClient(tx.config)
|
||||
tx.ChannelMonitor = NewChannelMonitorClient(tx.config)
|
||||
tx.ChannelMonitorHistory = NewChannelMonitorHistoryClient(tx.config)
|
||||
tx.ErrorPassthroughRule = NewErrorPassthroughRuleClient(tx.config)
|
||||
tx.Group = NewGroupClient(tx.config)
|
||||
tx.IdempotencyRecord = NewIdempotencyRecordClient(tx.config)
|
||||
|
||||
@@ -162,6 +162,8 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
|
||||
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
@@ -181,6 +183,8 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
||||
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@@ -216,6 +220,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
|
||||
@@ -249,6 +255,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -278,6 +286,8 @@ github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEv
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
@@ -310,6 +320,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
|
||||
396
backend/internal/handler/admin/channel_monitor_handler.go
Normal file
396
backend/internal/handler/admin/channel_monitor_handler.go
Normal file
@@ -0,0 +1,396 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
// monitorMaxPageSize 列表分页上限。
|
||||
monitorMaxPageSize = 100
|
||||
// monitorAPIKeyMaskPrefix 脱敏时保留的明文前缀长度。
|
||||
monitorAPIKeyMaskPrefix = 4
|
||||
// monitorAPIKeyMaskSuffix 脱敏后追加的占位字符串。
|
||||
monitorAPIKeyMaskSuffix = "***"
|
||||
)
|
||||
|
||||
// ChannelMonitorHandler 渠道监控管理后台 handler。
|
||||
type ChannelMonitorHandler struct {
|
||||
monitorService *service.ChannelMonitorService
|
||||
}
|
||||
|
||||
// NewChannelMonitorHandler 创建 handler。
|
||||
func NewChannelMonitorHandler(monitorService *service.ChannelMonitorService) *ChannelMonitorHandler {
|
||||
return &ChannelMonitorHandler{monitorService: monitorService}
|
||||
}
|
||||
|
||||
// --- Request / Response ---
|
||||
|
||||
type channelMonitorCreateRequest struct {
|
||||
Name string `json:"name" binding:"required,max=100"`
|
||||
Provider string `json:"provider" binding:"required,oneof=openai anthropic gemini"`
|
||||
Endpoint string `json:"endpoint" binding:"required,max=500"`
|
||||
APIKey string `json:"api_key" binding:"required,max=2000"`
|
||||
PrimaryModel string `json:"primary_model" binding:"required,max=200"`
|
||||
ExtraModels []string `json:"extra_models"`
|
||||
GroupName string `json:"group_name" binding:"max=100"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IntervalSeconds int `json:"interval_seconds" binding:"required,min=15,max=3600"`
|
||||
}
|
||||
|
||||
type channelMonitorUpdateRequest struct {
|
||||
Name *string `json:"name" binding:"omitempty,max=100"`
|
||||
Provider *string `json:"provider" binding:"omitempty,oneof=openai anthropic gemini"`
|
||||
Endpoint *string `json:"endpoint" binding:"omitempty,max=500"`
|
||||
APIKey *string `json:"api_key" binding:"omitempty,max=2000"`
|
||||
PrimaryModel *string `json:"primary_model" binding:"omitempty,max=200"`
|
||||
ExtraModels *[]string `json:"extra_models"`
|
||||
GroupName *string `json:"group_name" binding:"omitempty,max=100"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IntervalSeconds *int `json:"interval_seconds" binding:"omitempty,min=15,max=3600"`
|
||||
}
|
||||
|
||||
type channelMonitorResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
APIKeyMasked string `json:"api_key_masked"`
|
||||
APIKeyDecryptFailed bool `json:"api_key_decrypt_failed"`
|
||||
PrimaryModel string `json:"primary_model"`
|
||||
ExtraModels []string `json:"extra_models"`
|
||||
GroupName string `json:"group_name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
IntervalSeconds int `json:"interval_seconds"`
|
||||
LastCheckedAt *string `json:"last_checked_at"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
PrimaryStatus string `json:"primary_status"`
|
||||
PrimaryLatencyMs *int `json:"primary_latency_ms"`
|
||||
Availability7d float64 `json:"availability_7d"`
|
||||
ExtraModelsStatus []dto.ChannelMonitorExtraModelStatus `json:"extra_models_status"`
|
||||
}
|
||||
|
||||
type channelMonitorCheckResultResponse struct {
|
||||
Model string `json:"model"`
|
||||
Status string `json:"status"`
|
||||
LatencyMs *int `json:"latency_ms"`
|
||||
PingLatencyMs *int `json:"ping_latency_ms"`
|
||||
Message string `json:"message"`
|
||||
CheckedAt string `json:"checked_at"`
|
||||
}
|
||||
|
||||
type channelMonitorHistoryItemResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Model string `json:"model"`
|
||||
Status string `json:"status"`
|
||||
LatencyMs *int `json:"latency_ms"`
|
||||
PingLatencyMs *int `json:"ping_latency_ms"`
|
||||
Message string `json:"message"`
|
||||
CheckedAt string `json:"checked_at"`
|
||||
}
|
||||
|
||||
// maskAPIKey 对 API Key 明文做脱敏:前 4 字符 + "***",长度 ≤ 4 时只显示 "***"。
|
||||
func maskAPIKey(plain string) string {
|
||||
if len(plain) <= monitorAPIKeyMaskPrefix {
|
||||
return monitorAPIKeyMaskSuffix
|
||||
}
|
||||
return plain[:monitorAPIKeyMaskPrefix] + monitorAPIKeyMaskSuffix
|
||||
}
|
||||
|
||||
func channelMonitorToResponse(m *service.ChannelMonitor) *channelMonitorResponse {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
extras := m.ExtraModels
|
||||
if extras == nil {
|
||||
extras = []string{}
|
||||
}
|
||||
resp := &channelMonitorResponse{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Provider: m.Provider,
|
||||
Endpoint: m.Endpoint,
|
||||
APIKeyMasked: maskAPIKey(m.APIKey),
|
||||
APIKeyDecryptFailed: m.APIKeyDecryptFailed,
|
||||
PrimaryModel: m.PrimaryModel,
|
||||
ExtraModels: extras,
|
||||
GroupName: m.GroupName,
|
||||
Enabled: m.Enabled,
|
||||
IntervalSeconds: m.IntervalSeconds,
|
||||
CreatedBy: m.CreatedBy,
|
||||
CreatedAt: m.CreatedAt.UTC().Format(time.RFC3339),
|
||||
UpdatedAt: m.UpdatedAt.UTC().Format(time.RFC3339),
|
||||
// PrimaryStatus / PrimaryLatencyMs / Availability7d 由 List handler 在批量聚合后填充。
|
||||
}
|
||||
if m.LastCheckedAt != nil {
|
||||
s := m.LastCheckedAt.UTC().Format(time.RFC3339)
|
||||
resp.LastCheckedAt = &s
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func checkResultToResponse(r *service.CheckResult) channelMonitorCheckResultResponse {
|
||||
return channelMonitorCheckResultResponse{
|
||||
Model: r.Model,
|
||||
Status: r.Status,
|
||||
LatencyMs: r.LatencyMs,
|
||||
PingLatencyMs: r.PingLatencyMs,
|
||||
Message: r.Message,
|
||||
CheckedAt: r.CheckedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
func historyEntryToResponse(e *service.ChannelMonitorHistoryEntry) channelMonitorHistoryItemResponse {
|
||||
return channelMonitorHistoryItemResponse{
|
||||
ID: e.ID,
|
||||
Model: e.Model,
|
||||
Status: e.Status,
|
||||
LatencyMs: e.LatencyMs,
|
||||
PingLatencyMs: e.PingLatencyMs,
|
||||
Message: e.Message,
|
||||
CheckedAt: e.CheckedAt.UTC().Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseChannelMonitorID 提取并校验路径参数 :id(admin 与 user handler 共享)。
|
||||
// 校验失败时已写入 4xx 响应,调用方只需 return。
|
||||
func ParseChannelMonitorID(c *gin.Context) (int64, bool) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
response.ErrorFrom(c, infraerrors.BadRequest("INVALID_MONITOR_ID", "invalid monitor id"))
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
// parseListEnabled 解析 enabled query 参数:true/false 转为 *bool,空或非法则返回 nil。
|
||||
func parseListEnabled(raw string) *bool {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case "true", "1", "yes":
|
||||
v := true
|
||||
return &v
|
||||
case "false", "0", "no":
|
||||
v := false
|
||||
return &v
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
// List GET /api/v1/admin/channel-monitors
|
||||
func (h *ChannelMonitorHandler) List(c *gin.Context) {
|
||||
page, pageSize := response.ParsePagination(c)
|
||||
if pageSize > monitorMaxPageSize {
|
||||
pageSize = monitorMaxPageSize
|
||||
}
|
||||
|
||||
params := service.ChannelMonitorListParams{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
Provider: strings.TrimSpace(c.Query("provider")),
|
||||
Enabled: parseListEnabled(c.Query("enabled")),
|
||||
Search: strings.TrimSpace(c.Query("search")),
|
||||
}
|
||||
|
||||
items, total, err := h.monitorService.List(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
summaries := h.batchSummaryFor(c, items)
|
||||
out := make([]*channelMonitorResponse, 0, len(items))
|
||||
for _, m := range items {
|
||||
out = append(out, buildListItemResponse(m, summaries[m.ID]))
|
||||
}
|
||||
response.Paginated(c, out, total, page, pageSize)
|
||||
}
|
||||
|
||||
// batchSummaryFor 批量聚合 latest + 7d 可用率,避免每行 2 次 SQL(消除 N+1)。
|
||||
func (h *ChannelMonitorHandler) batchSummaryFor(c *gin.Context, items []*service.ChannelMonitor) map[int64]service.MonitorStatusSummary {
|
||||
ids := make([]int64, 0, len(items))
|
||||
primaryByID := make(map[int64]string, len(items))
|
||||
extrasByID := make(map[int64][]string, len(items))
|
||||
for _, m := range items {
|
||||
ids = append(ids, m.ID)
|
||||
primaryByID[m.ID] = m.PrimaryModel
|
||||
extrasByID[m.ID] = m.ExtraModels
|
||||
}
|
||||
return h.monitorService.BatchMonitorStatusSummary(c.Request.Context(), ids, primaryByID, extrasByID)
|
||||
}
|
||||
|
||||
// buildListItemResponse 把 monitor + summary 装成 admin list 的响应行。
|
||||
func buildListItemResponse(m *service.ChannelMonitor, summary service.MonitorStatusSummary) *channelMonitorResponse {
|
||||
resp := channelMonitorToResponse(m)
|
||||
resp.PrimaryStatus = summary.PrimaryStatus
|
||||
resp.PrimaryLatencyMs = summary.PrimaryLatencyMs
|
||||
resp.Availability7d = summary.Availability7d
|
||||
resp.ExtraModelsStatus = make([]dto.ChannelMonitorExtraModelStatus, 0, len(summary.ExtraModels))
|
||||
for _, e := range summary.ExtraModels {
|
||||
resp.ExtraModelsStatus = append(resp.ExtraModelsStatus, dto.ChannelMonitorExtraModelStatus{
|
||||
Model: e.Model,
|
||||
Status: e.Status,
|
||||
LatencyMs: e.LatencyMs,
|
||||
})
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// Get GET /api/v1/admin/channel-monitors/:id
|
||||
func (h *ChannelMonitorHandler) Get(c *gin.Context) {
|
||||
id, ok := ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m, err := h.monitorService.Get(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, channelMonitorToResponse(m))
|
||||
}
|
||||
|
||||
// Create POST /api/v1/admin/channel-monitors
|
||||
func (h *ChannelMonitorHandler) Create(c *gin.Context) {
|
||||
var req channelMonitorCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
subject, _ := middleware2.GetAuthSubjectFromContext(c)
|
||||
|
||||
enabled := true
|
||||
if req.Enabled != nil {
|
||||
enabled = *req.Enabled
|
||||
}
|
||||
|
||||
m, err := h.monitorService.Create(c.Request.Context(), service.ChannelMonitorCreateParams{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
Endpoint: req.Endpoint,
|
||||
APIKey: req.APIKey,
|
||||
PrimaryModel: req.PrimaryModel,
|
||||
ExtraModels: req.ExtraModels,
|
||||
GroupName: req.GroupName,
|
||||
Enabled: enabled,
|
||||
IntervalSeconds: req.IntervalSeconds,
|
||||
CreatedBy: subject.UserID,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Created(c, channelMonitorToResponse(m))
|
||||
}
|
||||
|
||||
// Update PUT /api/v1/admin/channel-monitors/:id
|
||||
func (h *ChannelMonitorHandler) Update(c *gin.Context) {
|
||||
id, ok := ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req channelMonitorUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorFrom(c, infraerrors.BadRequest("VALIDATION_ERROR", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
m, err := h.monitorService.Update(c.Request.Context(), id, service.ChannelMonitorUpdateParams{
|
||||
Name: req.Name,
|
||||
Provider: req.Provider,
|
||||
Endpoint: req.Endpoint,
|
||||
APIKey: req.APIKey,
|
||||
PrimaryModel: req.PrimaryModel,
|
||||
ExtraModels: req.ExtraModels,
|
||||
GroupName: req.GroupName,
|
||||
Enabled: req.Enabled,
|
||||
IntervalSeconds: req.IntervalSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, channelMonitorToResponse(m))
|
||||
}
|
||||
|
||||
// Delete DELETE /api/v1/admin/channel-monitors/:id
|
||||
func (h *ChannelMonitorHandler) Delete(c *gin.Context) {
|
||||
id, ok := ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.monitorService.Delete(c.Request.Context(), id); err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, nil)
|
||||
}
|
||||
|
||||
// Run POST /api/v1/admin/channel-monitors/:id/run
|
||||
func (h *ChannelMonitorHandler) Run(c *gin.Context) {
|
||||
id, ok := ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
results, err := h.monitorService.RunCheck(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
out := make([]channelMonitorCheckResultResponse, 0, len(results))
|
||||
for _, r := range results {
|
||||
out = append(out, checkResultToResponse(r))
|
||||
}
|
||||
response.Success(c, gin.H{"results": out})
|
||||
}
|
||||
|
||||
// History GET /api/v1/admin/channel-monitors/:id/history
|
||||
func (h *ChannelMonitorHandler) History(c *gin.Context) {
|
||||
id, ok := ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
limit := parseHistoryLimit(c.Query("limit"))
|
||||
model := strings.TrimSpace(c.Query("model"))
|
||||
|
||||
entries, err := h.monitorService.ListHistory(c.Request.Context(), id, model, limit)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
out := make([]channelMonitorHistoryItemResponse, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
out = append(out, historyEntryToResponse(e))
|
||||
}
|
||||
response.Success(c, gin.H{"items": out})
|
||||
}
|
||||
|
||||
// parseHistoryLimit 解析 history 接口的 limit query。
|
||||
// 使用 service 包的统一上下限常量,避免在 handler 重复定义同名魔法值。
|
||||
func parseHistoryLimit(raw string) int {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return service.MonitorHistoryDefaultLimit
|
||||
}
|
||||
v, err := strconv.Atoi(raw)
|
||||
if err != nil || v <= 0 {
|
||||
return service.MonitorHistoryDefaultLimit
|
||||
}
|
||||
if v > service.MonitorHistoryMaxLimit {
|
||||
return service.MonitorHistoryMaxLimit
|
||||
}
|
||||
return v
|
||||
}
|
||||
127
backend/internal/handler/channel_monitor_user_handler.go
Normal file
127
backend/internal/handler/channel_monitor_user_handler.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
|
||||
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ChannelMonitorUserHandler 渠道监控用户只读 handler。
|
||||
type ChannelMonitorUserHandler struct {
|
||||
monitorService *service.ChannelMonitorService
|
||||
}
|
||||
|
||||
// NewChannelMonitorUserHandler 创建 handler。
|
||||
func NewChannelMonitorUserHandler(monitorService *service.ChannelMonitorService) *ChannelMonitorUserHandler {
|
||||
return &ChannelMonitorUserHandler{monitorService: monitorService}
|
||||
}
|
||||
|
||||
// --- Response ---
|
||||
|
||||
type channelMonitorUserListItem struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
GroupName string `json:"group_name"`
|
||||
PrimaryModel string `json:"primary_model"`
|
||||
PrimaryStatus string `json:"primary_status"`
|
||||
PrimaryLatencyMs *int `json:"primary_latency_ms"`
|
||||
Availability7d float64 `json:"availability_7d"`
|
||||
ExtraModels []dto.ChannelMonitorExtraModelStatus `json:"extra_models"`
|
||||
}
|
||||
|
||||
type channelMonitorUserDetailResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
GroupName string `json:"group_name"`
|
||||
Models []channelMonitorUserModelStat `json:"models"`
|
||||
}
|
||||
|
||||
type channelMonitorUserModelStat struct {
|
||||
Model string `json:"model"`
|
||||
LatestStatus string `json:"latest_status"`
|
||||
LatestLatencyMs *int `json:"latest_latency_ms"`
|
||||
Availability7d float64 `json:"availability_7d"`
|
||||
Availability15d float64 `json:"availability_15d"`
|
||||
Availability30d float64 `json:"availability_30d"`
|
||||
AvgLatency7dMs *int `json:"avg_latency_7d_ms"`
|
||||
}
|
||||
|
||||
func userMonitorViewToItem(v *service.UserMonitorView) channelMonitorUserListItem {
|
||||
extras := make([]dto.ChannelMonitorExtraModelStatus, 0, len(v.ExtraModels))
|
||||
for _, e := range v.ExtraModels {
|
||||
extras = append(extras, dto.ChannelMonitorExtraModelStatus{
|
||||
Model: e.Model,
|
||||
Status: e.Status,
|
||||
LatencyMs: e.LatencyMs,
|
||||
})
|
||||
}
|
||||
return channelMonitorUserListItem{
|
||||
ID: v.ID,
|
||||
Name: v.Name,
|
||||
Provider: v.Provider,
|
||||
GroupName: v.GroupName,
|
||||
PrimaryModel: v.PrimaryModel,
|
||||
PrimaryStatus: v.PrimaryStatus,
|
||||
PrimaryLatencyMs: v.PrimaryLatencyMs,
|
||||
Availability7d: v.Availability7d,
|
||||
ExtraModels: extras,
|
||||
}
|
||||
}
|
||||
|
||||
func userMonitorDetailToResponse(d *service.UserMonitorDetail) *channelMonitorUserDetailResponse {
|
||||
models := make([]channelMonitorUserModelStat, 0, len(d.Models))
|
||||
for _, m := range d.Models {
|
||||
models = append(models, channelMonitorUserModelStat{
|
||||
Model: m.Model,
|
||||
LatestStatus: m.LatestStatus,
|
||||
LatestLatencyMs: m.LatestLatencyMs,
|
||||
Availability7d: m.Availability7d,
|
||||
Availability15d: m.Availability15d,
|
||||
Availability30d: m.Availability30d,
|
||||
AvgLatency7dMs: m.AvgLatency7dMs,
|
||||
})
|
||||
}
|
||||
return &channelMonitorUserDetailResponse{
|
||||
ID: d.ID,
|
||||
Name: d.Name,
|
||||
Provider: d.Provider,
|
||||
GroupName: d.GroupName,
|
||||
Models: models,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Handlers ---
|
||||
|
||||
// List GET /api/v1/channel-monitors
|
||||
func (h *ChannelMonitorUserHandler) List(c *gin.Context) {
|
||||
views, err := h.monitorService.ListUserView(c.Request.Context())
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
items := make([]channelMonitorUserListItem, 0, len(views))
|
||||
for _, v := range views {
|
||||
items = append(items, userMonitorViewToItem(v))
|
||||
}
|
||||
response.Success(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// GetStatus GET /api/v1/channel-monitors/:id/status
|
||||
func (h *ChannelMonitorUserHandler) GetStatus(c *gin.Context) {
|
||||
// 复用 admin.ParseChannelMonitorID 保持错误码与日志一致。
|
||||
id, ok := admin.ParseChannelMonitorID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
detail, err := h.monitorService.GetUserDetail(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
response.ErrorFrom(c, err)
|
||||
return
|
||||
}
|
||||
response.Success(c, userMonitorDetailToResponse(detail))
|
||||
}
|
||||
10
backend/internal/handler/dto/channel_monitor.go
Normal file
10
backend/internal/handler/dto/channel_monitor.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package dto
|
||||
|
||||
// ChannelMonitorExtraModelStatus 渠道监控附加模型最近一次状态。
|
||||
// 同时被 admin handler(List 响应)与 user handler(List 响应)复用,
|
||||
// 字段必须保持一致以保证前端拿到统一结构。
|
||||
type ChannelMonitorExtraModelStatus struct {
|
||||
Model string `json:"model"`
|
||||
Status string `json:"status"`
|
||||
LatencyMs *int `json:"latency_ms"`
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type AdminHandlers struct {
|
||||
APIKey *admin.AdminAPIKeyHandler
|
||||
ScheduledTest *admin.ScheduledTestHandler
|
||||
Channel *admin.ChannelHandler
|
||||
ChannelMonitor *admin.ChannelMonitorHandler
|
||||
Payment *admin.PaymentHandler
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ type Handlers struct {
|
||||
Redeem *RedeemHandler
|
||||
Subscription *SubscriptionHandler
|
||||
Announcement *AnnouncementHandler
|
||||
ChannelMonitor *ChannelMonitorUserHandler
|
||||
Admin *AdminHandlers
|
||||
Gateway *GatewayHandler
|
||||
OpenAIGateway *OpenAIGatewayHandler
|
||||
|
||||
@@ -34,6 +34,7 @@ func ProvideAdminHandlers(
|
||||
apiKeyHandler *admin.AdminAPIKeyHandler,
|
||||
scheduledTestHandler *admin.ScheduledTestHandler,
|
||||
channelHandler *admin.ChannelHandler,
|
||||
channelMonitorHandler *admin.ChannelMonitorHandler,
|
||||
paymentHandler *admin.PaymentHandler,
|
||||
) *AdminHandlers {
|
||||
return &AdminHandlers{
|
||||
@@ -62,6 +63,7 @@ func ProvideAdminHandlers(
|
||||
APIKey: apiKeyHandler,
|
||||
ScheduledTest: scheduledTestHandler,
|
||||
Channel: channelHandler,
|
||||
ChannelMonitor: channelMonitorHandler,
|
||||
Payment: paymentHandler,
|
||||
}
|
||||
}
|
||||
@@ -85,6 +87,7 @@ func ProvideHandlers(
|
||||
redeemHandler *RedeemHandler,
|
||||
subscriptionHandler *SubscriptionHandler,
|
||||
announcementHandler *AnnouncementHandler,
|
||||
channelMonitorUserHandler *ChannelMonitorUserHandler,
|
||||
adminHandlers *AdminHandlers,
|
||||
gatewayHandler *GatewayHandler,
|
||||
openaiGatewayHandler *OpenAIGatewayHandler,
|
||||
@@ -103,6 +106,7 @@ func ProvideHandlers(
|
||||
Redeem: redeemHandler,
|
||||
Subscription: subscriptionHandler,
|
||||
Announcement: announcementHandler,
|
||||
ChannelMonitor: channelMonitorUserHandler,
|
||||
Admin: adminHandlers,
|
||||
Gateway: gatewayHandler,
|
||||
OpenAIGateway: openaiGatewayHandler,
|
||||
@@ -123,6 +127,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewRedeemHandler,
|
||||
NewSubscriptionHandler,
|
||||
NewAnnouncementHandler,
|
||||
NewChannelMonitorUserHandler,
|
||||
NewGatewayHandler,
|
||||
NewOpenAIGatewayHandler,
|
||||
NewTotpHandler,
|
||||
@@ -156,6 +161,7 @@ var ProviderSet = wire.NewSet(
|
||||
admin.NewAdminAPIKeyHandler,
|
||||
admin.NewScheduledTestHandler,
|
||||
admin.NewChannelHandler,
|
||||
admin.NewChannelMonitorHandler,
|
||||
admin.NewPaymentHandler,
|
||||
|
||||
// AdminHandlers and Handlers constructors
|
||||
|
||||
450
backend/internal/repository/channel_monitor_repo.go
Normal file
450
backend/internal/repository/channel_monitor_repo.go
Normal file
@@ -0,0 +1,450 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitor"
|
||||
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
// channelMonitorRepository 实现 service.ChannelMonitorRepository。
|
||||
//
|
||||
// 选型说明:
|
||||
// - CRUD 走 ent,复用项目的事务上下文支持
|
||||
// - 聚合查询(latest per model / availability)走原生 SQL,避免 ent 在 GROUP BY 上
|
||||
// 的样板代码,并保证索引能被命中
|
||||
type channelMonitorRepository struct {
|
||||
client *dbent.Client
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewChannelMonitorRepository 创建仓储实例。
|
||||
func NewChannelMonitorRepository(client *dbent.Client, db *sql.DB) service.ChannelMonitorRepository {
|
||||
return &channelMonitorRepository{client: client, db: db}
|
||||
}
|
||||
|
||||
// ---------- CRUD ----------
|
||||
|
||||
func (r *channelMonitorRepository) Create(ctx context.Context, m *service.ChannelMonitor) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
builder := client.ChannelMonitor.Create().
|
||||
SetName(m.Name).
|
||||
SetProvider(channelmonitor.Provider(m.Provider)).
|
||||
SetEndpoint(m.Endpoint).
|
||||
SetAPIKeyEncrypted(m.APIKey). // 调用方传入的已是密文
|
||||
SetPrimaryModel(m.PrimaryModel).
|
||||
SetExtraModels(emptySliceIfNil(m.ExtraModels)).
|
||||
SetGroupName(m.GroupName).
|
||||
SetEnabled(m.Enabled).
|
||||
SetIntervalSeconds(m.IntervalSeconds).
|
||||
SetCreatedBy(m.CreatedBy)
|
||||
|
||||
created, err := builder.Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorNotFound, nil)
|
||||
}
|
||||
m.ID = created.ID
|
||||
m.CreatedAt = created.CreatedAt
|
||||
m.UpdatedAt = created.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) GetByID(ctx context.Context, id int64) (*service.ChannelMonitor, error) {
|
||||
row, err := r.client.ChannelMonitor.Query().
|
||||
Where(channelmonitor.IDEQ(id)).
|
||||
Only(ctx)
|
||||
if err != nil {
|
||||
return nil, translatePersistenceError(err, service.ErrChannelMonitorNotFound, nil)
|
||||
}
|
||||
return entToServiceMonitor(row), nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) Update(ctx context.Context, m *service.ChannelMonitor) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
updater := client.ChannelMonitor.UpdateOneID(m.ID).
|
||||
SetName(m.Name).
|
||||
SetProvider(channelmonitor.Provider(m.Provider)).
|
||||
SetEndpoint(m.Endpoint).
|
||||
SetAPIKeyEncrypted(m.APIKey).
|
||||
SetPrimaryModel(m.PrimaryModel).
|
||||
SetExtraModels(emptySliceIfNil(m.ExtraModels)).
|
||||
SetGroupName(m.GroupName).
|
||||
SetEnabled(m.Enabled).
|
||||
SetIntervalSeconds(m.IntervalSeconds)
|
||||
|
||||
updated, err := updater.Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorNotFound, nil)
|
||||
}
|
||||
m.UpdatedAt = updated.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) Delete(ctx context.Context, id int64) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
if err := client.ChannelMonitor.DeleteOneID(id).Exec(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorNotFound, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) List(ctx context.Context, params service.ChannelMonitorListParams) ([]*service.ChannelMonitor, int64, error) {
|
||||
q := r.client.ChannelMonitor.Query()
|
||||
if params.Provider != "" {
|
||||
q = q.Where(channelmonitor.ProviderEQ(channelmonitor.Provider(params.Provider)))
|
||||
}
|
||||
if params.Enabled != nil {
|
||||
q = q.Where(channelmonitor.EnabledEQ(*params.Enabled))
|
||||
}
|
||||
if s := strings.TrimSpace(params.Search); s != "" {
|
||||
q = q.Where(channelmonitor.Or(
|
||||
channelmonitor.NameContainsFold(s),
|
||||
channelmonitor.GroupNameContainsFold(s),
|
||||
channelmonitor.PrimaryModelContainsFold(s),
|
||||
))
|
||||
}
|
||||
|
||||
total, err := q.Count(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("count monitors: %w", err)
|
||||
}
|
||||
|
||||
pageSize := params.PageSize
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
page := params.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
rows, err := q.
|
||||
Order(dbent.Desc(channelmonitor.FieldID)).
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("list monitors: %w", err)
|
||||
}
|
||||
|
||||
out := make([]*service.ChannelMonitor, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, entToServiceMonitor(row))
|
||||
}
|
||||
return out, int64(total), nil
|
||||
}
|
||||
|
||||
// ---------- 调度器辅助 ----------
|
||||
|
||||
func (r *channelMonitorRepository) ListEnabled(ctx context.Context) ([]*service.ChannelMonitor, error) {
|
||||
rows, err := r.client.ChannelMonitor.Query().
|
||||
Where(channelmonitor.EnabledEQ(true)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list enabled monitors: %w", err)
|
||||
}
|
||||
out := make([]*service.ChannelMonitor, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, entToServiceMonitor(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) MarkChecked(ctx context.Context, id int64, checkedAt time.Time) error {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
if err := client.ChannelMonitor.UpdateOneID(id).
|
||||
SetLastCheckedAt(checkedAt).
|
||||
Exec(ctx); err != nil {
|
||||
return translatePersistenceError(err, service.ErrChannelMonitorNotFound, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) InsertHistoryBatch(ctx context.Context, rows []*service.ChannelMonitorHistoryRow) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
client := clientFromContext(ctx, r.client)
|
||||
bulk := make([]*dbent.ChannelMonitorHistoryCreate, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
c := client.ChannelMonitorHistory.Create().
|
||||
SetMonitorID(row.MonitorID).
|
||||
SetModel(row.Model).
|
||||
SetStatus(channelmonitorhistory.Status(row.Status)).
|
||||
SetMessage(row.Message).
|
||||
SetCheckedAt(row.CheckedAt)
|
||||
if row.LatencyMs != nil {
|
||||
c = c.SetLatencyMs(*row.LatencyMs)
|
||||
}
|
||||
if row.PingLatencyMs != nil {
|
||||
c = c.SetPingLatencyMs(*row.PingLatencyMs)
|
||||
}
|
||||
bulk = append(bulk, c)
|
||||
}
|
||||
if _, err := client.ChannelMonitorHistory.CreateBulk(bulk...).Save(ctx); err != nil {
|
||||
return fmt.Errorf("insert history bulk: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *channelMonitorRepository) DeleteHistoryBefore(ctx context.Context, before time.Time) (int64, error) {
|
||||
client := clientFromContext(ctx, r.client)
|
||||
n, err := client.ChannelMonitorHistory.Delete().
|
||||
Where(channelmonitorhistory.CheckedAtLT(before)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("delete history before: %w", err)
|
||||
}
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
// ListHistory 按 checked_at 倒序返回某个监控的最近 N 条历史记录。
|
||||
// model 为空时不过滤;非空时只返回该模型的记录。
|
||||
func (r *channelMonitorRepository) ListHistory(ctx context.Context, monitorID int64, model string, limit int) ([]*service.ChannelMonitorHistoryEntry, error) {
|
||||
q := r.client.ChannelMonitorHistory.Query().
|
||||
Where(channelmonitorhistory.MonitorIDEQ(monitorID))
|
||||
if strings.TrimSpace(model) != "" {
|
||||
q = q.Where(channelmonitorhistory.ModelEQ(model))
|
||||
}
|
||||
rows, err := q.
|
||||
Order(dbent.Desc(channelmonitorhistory.FieldCheckedAt)).
|
||||
Limit(limit).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list history: %w", err)
|
||||
}
|
||||
out := make([]*service.ChannelMonitorHistoryEntry, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
entry := &service.ChannelMonitorHistoryEntry{
|
||||
ID: row.ID,
|
||||
Model: row.Model,
|
||||
Status: string(row.Status),
|
||||
LatencyMs: row.LatencyMs,
|
||||
PingLatencyMs: row.PingLatencyMs,
|
||||
Message: row.Message,
|
||||
CheckedAt: row.CheckedAt,
|
||||
}
|
||||
out = append(out, entry)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------- 用户视图聚合(原生 SQL) ----------
|
||||
|
||||
// ListLatestPerModel 用 DISTINCT ON 取每个 (monitor_id, model) 的最近一条记录。
|
||||
// 借助 (monitor_id, model, checked_at DESC) 索引可走 Index Scan。
|
||||
func (r *channelMonitorRepository) ListLatestPerModel(ctx context.Context, monitorID int64) ([]*service.ChannelMonitorLatest, error) {
|
||||
const q = `
|
||||
SELECT DISTINCT ON (model)
|
||||
model, status, latency_ms, checked_at
|
||||
FROM channel_monitor_histories
|
||||
WHERE monitor_id = $1
|
||||
ORDER BY model, checked_at DESC
|
||||
`
|
||||
rows, err := r.db.QueryContext(ctx, q, monitorID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query latest per model: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
out := make([]*service.ChannelMonitorLatest, 0)
|
||||
for rows.Next() {
|
||||
l := &service.ChannelMonitorLatest{}
|
||||
var latency sql.NullInt64
|
||||
if err := rows.Scan(&l.Model, &l.Status, &latency, &l.CheckedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan latest row: %w", err)
|
||||
}
|
||||
if latency.Valid {
|
||||
v := int(latency.Int64)
|
||||
l.LatencyMs = &v
|
||||
}
|
||||
out = append(out, l)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ComputeAvailability 计算指定窗口内每个模型的可用率与平均延迟。
|
||||
// "可用" = status IN (operational, degraded)。
|
||||
func (r *channelMonitorRepository) ComputeAvailability(ctx context.Context, monitorID int64, windowDays int) ([]*service.ChannelMonitorAvailability, error) {
|
||||
if windowDays <= 0 {
|
||||
windowDays = 7
|
||||
}
|
||||
const q = `
|
||||
SELECT
|
||||
model,
|
||||
COUNT(*) AS total_checks,
|
||||
COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok_checks,
|
||||
AVG(latency_ms) FILTER (WHERE latency_ms IS NOT NULL) AS avg_latency_ms
|
||||
FROM channel_monitor_histories
|
||||
WHERE monitor_id = $1
|
||||
AND checked_at >= $2
|
||||
GROUP BY model
|
||||
`
|
||||
from := time.Now().AddDate(0, 0, -windowDays)
|
||||
rows, err := r.db.QueryContext(ctx, q, monitorID, from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query availability: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
out := make([]*service.ChannelMonitorAvailability, 0)
|
||||
for rows.Next() {
|
||||
row, err := scanAvailabilityRow(rows, windowDays)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, row)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// scanAvailabilityRow 把单行 (model, total, ok, avg_latency) 扫描为 ChannelMonitorAvailability。
|
||||
// 仅服务于 ComputeAvailability(4 列);批量版本因为多一列 monitor_id 直接 inline 调 finalizeAvailabilityRow。
|
||||
func scanAvailabilityRow(rows interface{ Scan(...any) error }, windowDays int) (*service.ChannelMonitorAvailability, error) {
|
||||
row := &service.ChannelMonitorAvailability{WindowDays: windowDays}
|
||||
var avgLatency sql.NullFloat64
|
||||
if err := rows.Scan(&row.Model, &row.TotalChecks, &row.OperationalChecks, &avgLatency); err != nil {
|
||||
return nil, fmt.Errorf("scan availability row: %w", err)
|
||||
}
|
||||
finalizeAvailabilityRow(row, avgLatency)
|
||||
return row, nil
|
||||
}
|
||||
|
||||
// finalizeAvailabilityRow 根据 OperationalChecks/TotalChecks 算出可用率,
|
||||
// 并把 sql.NullFloat64 的平均延迟解包为 *int。两处复用避免维护漂移。
|
||||
func finalizeAvailabilityRow(row *service.ChannelMonitorAvailability, avgLatency sql.NullFloat64) {
|
||||
if row.TotalChecks > 0 {
|
||||
row.AvailabilityPct = float64(row.OperationalChecks) * 100.0 / float64(row.TotalChecks)
|
||||
}
|
||||
if avgLatency.Valid {
|
||||
v := int(avgLatency.Float64)
|
||||
row.AvgLatencyMs = &v
|
||||
}
|
||||
}
|
||||
|
||||
// ListLatestForMonitorIDs 一次性查询多个监控的"每个 (monitor_id, model) 最近一条"记录。
|
||||
// 利用 PG 的 DISTINCT ON 特性,借助 (monitor_id, model, checked_at DESC) 索引可走 Index Scan。
|
||||
func (r *channelMonitorRepository) ListLatestForMonitorIDs(ctx context.Context, ids []int64) (map[int64][]*service.ChannelMonitorLatest, error) {
|
||||
out := make(map[int64][]*service.ChannelMonitorLatest, len(ids))
|
||||
if len(ids) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
const q = `
|
||||
SELECT DISTINCT ON (monitor_id, model)
|
||||
monitor_id, model, status, latency_ms, checked_at
|
||||
FROM channel_monitor_histories
|
||||
WHERE monitor_id = ANY($1)
|
||||
ORDER BY monitor_id, model, checked_at DESC
|
||||
`
|
||||
rows, err := r.db.QueryContext(ctx, q, pq.Array(ids))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query latest batch: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
var monitorID int64
|
||||
l := &service.ChannelMonitorLatest{}
|
||||
var latency sql.NullInt64
|
||||
if err := rows.Scan(&monitorID, &l.Model, &l.Status, &latency, &l.CheckedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan latest batch row: %w", err)
|
||||
}
|
||||
if latency.Valid {
|
||||
v := int(latency.Int64)
|
||||
l.LatencyMs = &v
|
||||
}
|
||||
out[monitorID] = append(out[monitorID], l)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ComputeAvailabilityForMonitors 一次性计算多个监控在某个窗口内的每模型可用率与平均延迟。
|
||||
func (r *channelMonitorRepository) ComputeAvailabilityForMonitors(ctx context.Context, ids []int64, windowDays int) (map[int64][]*service.ChannelMonitorAvailability, error) {
|
||||
out := make(map[int64][]*service.ChannelMonitorAvailability, len(ids))
|
||||
if len(ids) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
if windowDays <= 0 {
|
||||
windowDays = 7
|
||||
}
|
||||
const q = `
|
||||
SELECT
|
||||
monitor_id,
|
||||
model,
|
||||
COUNT(*) AS total_checks,
|
||||
COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok_checks,
|
||||
AVG(latency_ms) FILTER (WHERE latency_ms IS NOT NULL) AS avg_latency_ms
|
||||
FROM channel_monitor_histories
|
||||
WHERE monitor_id = ANY($1)
|
||||
AND checked_at >= $2
|
||||
GROUP BY monitor_id, model
|
||||
`
|
||||
from := time.Now().AddDate(0, 0, -windowDays)
|
||||
rows, err := r.db.QueryContext(ctx, q, pq.Array(ids), from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query availability batch: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
for rows.Next() {
|
||||
var monitorID int64
|
||||
row := &service.ChannelMonitorAvailability{WindowDays: windowDays}
|
||||
var avgLatency sql.NullFloat64
|
||||
if err := rows.Scan(&monitorID, &row.Model, &row.TotalChecks, &row.OperationalChecks, &avgLatency); err != nil {
|
||||
return nil, fmt.Errorf("scan availability batch row: %w", err)
|
||||
}
|
||||
// 批量查询多了首列 monitor_id;其余字段的可用率/平均延迟换算与单 monitor 版本一致,
|
||||
// 抽出 finalizeAvailabilityRow 复用,避免两处分别维护除法与 NullFloat 解包。
|
||||
finalizeAvailabilityRow(row, avgLatency)
|
||||
out[monitorID] = append(out[monitorID], row)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
func entToServiceMonitor(row *dbent.ChannelMonitor) *service.ChannelMonitor {
|
||||
if row == nil {
|
||||
return nil
|
||||
}
|
||||
extras := row.ExtraModels
|
||||
if extras == nil {
|
||||
extras = []string{}
|
||||
}
|
||||
return &service.ChannelMonitor{
|
||||
ID: row.ID,
|
||||
Name: row.Name,
|
||||
Provider: string(row.Provider),
|
||||
Endpoint: row.Endpoint,
|
||||
APIKey: row.APIKeyEncrypted, // 仍为密文,service 层负责解密
|
||||
PrimaryModel: row.PrimaryModel,
|
||||
ExtraModels: extras,
|
||||
GroupName: row.GroupName,
|
||||
Enabled: row.Enabled,
|
||||
IntervalSeconds: row.IntervalSeconds,
|
||||
LastCheckedAt: row.LastCheckedAt,
|
||||
CreatedBy: row.CreatedBy,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func emptySliceIfNil(in []string) []string {
|
||||
if in == nil {
|
||||
return []string{}
|
||||
}
|
||||
return in
|
||||
}
|
||||
@@ -89,6 +89,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewErrorPassthroughRepository,
|
||||
NewTLSFingerprintProfileRepository,
|
||||
NewChannelRepository,
|
||||
NewChannelMonitorRepository,
|
||||
|
||||
// Cache implementations
|
||||
NewGatewayCache,
|
||||
|
||||
@@ -88,6 +88,9 @@ func RegisterAdminRoutes(
|
||||
|
||||
// 渠道管理
|
||||
registerChannelRoutes(admin, h)
|
||||
|
||||
// 渠道监控
|
||||
registerChannelMonitorRoutes(admin, h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,3 +567,16 @@ func registerChannelRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
||||
channels.DELETE("/:id", h.Admin.Channel.Delete)
|
||||
}
|
||||
}
|
||||
|
||||
func registerChannelMonitorRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
|
||||
monitors := admin.Group("/channel-monitors")
|
||||
{
|
||||
monitors.GET("", h.Admin.ChannelMonitor.List)
|
||||
monitors.POST("", h.Admin.ChannelMonitor.Create)
|
||||
monitors.GET("/:id", h.Admin.ChannelMonitor.Get)
|
||||
monitors.PUT("/:id", h.Admin.ChannelMonitor.Update)
|
||||
monitors.DELETE("/:id", h.Admin.ChannelMonitor.Delete)
|
||||
monitors.POST("/:id/run", h.Admin.ChannelMonitor.Run)
|
||||
monitors.GET("/:id/history", h.Admin.ChannelMonitor.History)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,5 +103,12 @@ func RegisterUserRoutes(
|
||||
subscriptions.GET("/progress", h.Subscription.GetProgress)
|
||||
subscriptions.GET("/summary", h.Subscription.GetSummary)
|
||||
}
|
||||
|
||||
// 渠道监控(用户只读)
|
||||
monitors := authenticated.Group("/channel-monitors")
|
||||
{
|
||||
monitors.GET("", h.ChannelMonitor.List)
|
||||
monitors.GET("/:id/status", h.ChannelMonitor.GetStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
217
backend/internal/service/channel_monitor_aggregator.go
Normal file
217
backend/internal/service/channel_monitor_aggregator.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// 渠道监控聚合层:把 latest + availability 拼成 admin/user 视图所需的 summary / detail。
|
||||
// 所有方法都遵守"失败仅日志,返回零值"的原则,避免 N+1 查询失败拖垮列表渲染。
|
||||
|
||||
// BatchMonitorStatusSummary 批量聚合多个监控的 latest + 7d 可用率(admin/user list 用,消除 N+1)。
|
||||
// 失败时返回空 map,错误仅日志,不影响列表渲染。
|
||||
//
|
||||
// 参数:
|
||||
// - ids: 要聚合的 monitor ID 列表
|
||||
// - primaryByID: monitor ID -> primary model(用于读 7d 可用率与 latest 状态)
|
||||
// - extrasByID: monitor ID -> extra models 列表(用于读 latest 状态填充 ExtraModels)
|
||||
func (s *ChannelMonitorService) BatchMonitorStatusSummary(
|
||||
ctx context.Context,
|
||||
ids []int64,
|
||||
primaryByID map[int64]string,
|
||||
extrasByID map[int64][]string,
|
||||
) map[int64]MonitorStatusSummary {
|
||||
out := make(map[int64]MonitorStatusSummary, len(ids))
|
||||
if len(ids) == 0 {
|
||||
return out
|
||||
}
|
||||
latestMap, err := s.repo.ListLatestForMonitorIDs(ctx, ids)
|
||||
if err != nil {
|
||||
slog.Warn("channel_monitor: batch load latest failed", "error", err)
|
||||
latestMap = map[int64][]*ChannelMonitorLatest{}
|
||||
}
|
||||
availMap, err := s.repo.ComputeAvailabilityForMonitors(ctx, ids, monitorAvailability7Days)
|
||||
if err != nil {
|
||||
slog.Warn("channel_monitor: batch compute availability failed", "error", err)
|
||||
availMap = map[int64][]*ChannelMonitorAvailability{}
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
out[id] = buildStatusSummary(
|
||||
indexLatestByModel(latestMap[id]),
|
||||
indexAvailabilityByModel(availMap[id]),
|
||||
primaryByID[id],
|
||||
extrasByID[id],
|
||||
)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ListUserView 用户只读视图:列出所有 enabled 监控的概览。
|
||||
// 使用批量聚合接口避免 N+1:1 次查 monitors,1 次查 latest(所有 monitor),1 次查 availability。
|
||||
func (s *ChannelMonitorService) ListUserView(ctx context.Context) ([]*UserMonitorView, error) {
|
||||
monitors, err := s.repo.ListEnabled(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list enabled monitors: %w", err)
|
||||
}
|
||||
if len(monitors) == 0 {
|
||||
return []*UserMonitorView{}, nil
|
||||
}
|
||||
|
||||
ids := make([]int64, 0, len(monitors))
|
||||
primaryByID := make(map[int64]string, len(monitors))
|
||||
extrasByID := make(map[int64][]string, len(monitors))
|
||||
for _, m := range monitors {
|
||||
ids = append(ids, m.ID)
|
||||
primaryByID[m.ID] = m.PrimaryModel
|
||||
extrasByID[m.ID] = m.ExtraModels
|
||||
}
|
||||
summaries := s.BatchMonitorStatusSummary(ctx, ids, primaryByID, extrasByID)
|
||||
|
||||
views := make([]*UserMonitorView, 0, len(monitors))
|
||||
for _, m := range monitors {
|
||||
summary := summaries[m.ID]
|
||||
views = append(views, buildUserViewFromSummary(m, summary))
|
||||
}
|
||||
return views, nil
|
||||
}
|
||||
|
||||
// GetUserDetail 用户只读视图:单个监控详情(每个模型 7d/15d/30d 可用率与平均延迟)。
|
||||
// 不暴露 api_key。
|
||||
func (s *ChannelMonitorService) GetUserDetail(ctx context.Context, id int64) (*UserMonitorDetail, error) {
|
||||
m, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !m.Enabled {
|
||||
return nil, ErrChannelMonitorNotFound
|
||||
}
|
||||
|
||||
latest, err := s.repo.ListLatestPerModel(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list latest per model: %w", err)
|
||||
}
|
||||
availMap, err := s.collectAvailabilityWindows(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
models := mergeModelDetails(m, latest, availMap)
|
||||
return &UserMonitorDetail{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Provider: m.Provider,
|
||||
GroupName: m.GroupName,
|
||||
Models: models,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// collectAvailabilityWindows 一次性查询 7/15/30 天三个窗口,按模型组织。
|
||||
func (s *ChannelMonitorService) collectAvailabilityWindows(ctx context.Context, monitorID int64) (map[int]map[string]*ChannelMonitorAvailability, error) {
|
||||
out := make(map[int]map[string]*ChannelMonitorAvailability, 3)
|
||||
windows := []int{monitorAvailability7Days, monitorAvailability15Days, monitorAvailability30Days}
|
||||
for _, w := range windows {
|
||||
rows, err := s.repo.ComputeAvailability(ctx, monitorID, w)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("compute availability %dd: %w", w, err)
|
||||
}
|
||||
out[w] = indexAvailabilityByModel(rows)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ---------- 纯函数 helper(无 IO,可在 batch / 单 monitor / detail 路径复用)----------
|
||||
|
||||
// indexLatestByModel 把 latest 切片按 model 索引(小工具,避免在 hot path 重复写)。
|
||||
func indexLatestByModel(rows []*ChannelMonitorLatest) map[string]*ChannelMonitorLatest {
|
||||
m := make(map[string]*ChannelMonitorLatest, len(rows))
|
||||
for _, r := range rows {
|
||||
m[r.Model] = r
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// indexAvailabilityByModel 把 availability 切片按 model 索引。
|
||||
func indexAvailabilityByModel(rows []*ChannelMonitorAvailability) map[string]*ChannelMonitorAvailability {
|
||||
m := make(map[string]*ChannelMonitorAvailability, len(rows))
|
||||
for _, r := range rows {
|
||||
m[r.Model] = r
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// buildStatusSummary 由 latest + availability 字典构造 MonitorStatusSummary。
|
||||
// 不做任何 IO,纯组装,便于在 batch 与单 monitor 路径复用。
|
||||
func buildStatusSummary(
|
||||
latestByModel map[string]*ChannelMonitorLatest,
|
||||
availByModel map[string]*ChannelMonitorAvailability,
|
||||
primary string,
|
||||
extras []string,
|
||||
) MonitorStatusSummary {
|
||||
summary := MonitorStatusSummary{ExtraModels: make([]ExtraModelStatus, 0, len(extras))}
|
||||
if primary != "" {
|
||||
if l, ok := latestByModel[primary]; ok {
|
||||
summary.PrimaryStatus = l.Status
|
||||
summary.PrimaryLatencyMs = l.LatencyMs
|
||||
}
|
||||
if a, ok := availByModel[primary]; ok {
|
||||
summary.Availability7d = a.AvailabilityPct
|
||||
}
|
||||
}
|
||||
for _, model := range extras {
|
||||
entry := ExtraModelStatus{Model: model}
|
||||
if l, ok := latestByModel[model]; ok {
|
||||
entry.Status = l.Status
|
||||
entry.LatencyMs = l.LatencyMs
|
||||
}
|
||||
summary.ExtraModels = append(summary.ExtraModels, entry)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
// buildUserViewFromSummary 用预聚合好的 MonitorStatusSummary 装填 UserMonitorView(无 IO)。
|
||||
func buildUserViewFromSummary(m *ChannelMonitor, summary MonitorStatusSummary) *UserMonitorView {
|
||||
return &UserMonitorView{
|
||||
ID: m.ID,
|
||||
Name: m.Name,
|
||||
Provider: m.Provider,
|
||||
GroupName: m.GroupName,
|
||||
PrimaryModel: m.PrimaryModel,
|
||||
PrimaryStatus: summary.PrimaryStatus,
|
||||
PrimaryLatencyMs: summary.PrimaryLatencyMs,
|
||||
Availability7d: summary.Availability7d,
|
||||
ExtraModels: summary.ExtraModels,
|
||||
}
|
||||
}
|
||||
|
||||
// mergeModelDetails 合并 latest + availability 三个窗口为 ModelDetail 列表。
|
||||
// 复用 indexLatestByModel,避免在多处重复写 build map 逻辑。
|
||||
func mergeModelDetails(
|
||||
m *ChannelMonitor,
|
||||
latest []*ChannelMonitorLatest,
|
||||
availMap map[int]map[string]*ChannelMonitorAvailability,
|
||||
) []ModelDetail {
|
||||
all := append([]string{m.PrimaryModel}, m.ExtraModels...)
|
||||
latestByModel := indexLatestByModel(latest)
|
||||
out := make([]ModelDetail, 0, len(all))
|
||||
for _, model := range all {
|
||||
d := ModelDetail{Model: model}
|
||||
if l, ok := latestByModel[model]; ok {
|
||||
d.LatestStatus = l.Status
|
||||
d.LatestLatencyMs = l.LatencyMs
|
||||
}
|
||||
if a, ok := availMap[monitorAvailability7Days][model]; ok {
|
||||
d.Availability7d = a.AvailabilityPct
|
||||
d.AvgLatency7dMs = a.AvgLatencyMs
|
||||
}
|
||||
if a, ok := availMap[monitorAvailability15Days][model]; ok {
|
||||
d.Availability15d = a.AvailabilityPct
|
||||
}
|
||||
if a, ok := availMap[monitorAvailability30Days][model]; ok {
|
||||
d.Availability30d = a.AvailabilityPct
|
||||
}
|
||||
out = append(out, d)
|
||||
}
|
||||
return out
|
||||
}
|
||||
80
backend/internal/service/channel_monitor_challenge.go
Normal file
80
backend/internal/service/channel_monitor_challenge.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// monitorChallengePromptTemplate 1:1 复刻 BingZi-233/check-cx 的 few-shot 模板。
|
||||
const monitorChallengePromptTemplate = `Calculate and respond with ONLY the number, nothing else.
|
||||
|
||||
Q: 3 + 5 = ?
|
||||
A: 8
|
||||
|
||||
Q: 12 - 7 = ?
|
||||
A: 5
|
||||
|
||||
Q: %d %s %d = ?
|
||||
A:`
|
||||
|
||||
// monitorChallengeNumberRegex 提取响应中的所有整数(含负号)。
|
||||
var monitorChallengeNumberRegex = regexp.MustCompile(`-?\d+`)
|
||||
|
||||
// monitorChallenge 一次 challenge 的 prompt + 期望答案。
|
||||
type monitorChallenge struct {
|
||||
Prompt string
|
||||
Expected string
|
||||
}
|
||||
|
||||
// generateChallenge 生成一次随机算术 challenge:
|
||||
// - 随机两个 [monitorChallengeMin, monitorChallengeMax] 整数
|
||||
// - 50% 加 / 50% 减;减法用 max - min 保证非负
|
||||
// - 渲染 few-shot 模板
|
||||
//
|
||||
// 不强求加密随机:math/rand/v2 足够分散,避免 crypto/rand 的开销。
|
||||
func generateChallenge() monitorChallenge {
|
||||
a := randIntInRange(monitorChallengeMin, monitorChallengeMax)
|
||||
b := randIntInRange(monitorChallengeMin, monitorChallengeMax)
|
||||
|
||||
if rand.IntN(2) == 0 { //nolint:gosec // 仅用于生成测试问题,无安全影响
|
||||
// 加法
|
||||
return monitorChallenge{
|
||||
Prompt: fmt.Sprintf(monitorChallengePromptTemplate, a, "+", b),
|
||||
Expected: strconv.Itoa(a + b),
|
||||
}
|
||||
}
|
||||
|
||||
// 减法,保证非负
|
||||
hi, lo := a, b
|
||||
if lo > hi {
|
||||
hi, lo = lo, hi
|
||||
}
|
||||
return monitorChallenge{
|
||||
Prompt: fmt.Sprintf(monitorChallengePromptTemplate, hi, "-", lo),
|
||||
Expected: strconv.Itoa(hi - lo),
|
||||
}
|
||||
}
|
||||
|
||||
// randIntInRange 返回 [min, max] 闭区间的随机整数。
|
||||
func randIntInRange(minVal, maxVal int) int {
|
||||
if maxVal <= minVal {
|
||||
return minVal
|
||||
}
|
||||
return minVal + rand.IntN(maxVal-minVal+1) //nolint:gosec
|
||||
}
|
||||
|
||||
// validateChallenge 在响应文本中查找 expected 整数答案,返回是否通过校验。
|
||||
func validateChallenge(responseText, expected string) bool {
|
||||
if responseText == "" || expected == "" {
|
||||
return false
|
||||
}
|
||||
matches := monitorChallengeNumberRegex.FindAllString(responseText, -1)
|
||||
for _, m := range matches {
|
||||
if m == expected {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
299
backend/internal/service/channel_monitor_checker.go
Normal file
299
backend/internal/service/channel_monitor_checker.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// monitorHTTPClient 共享一个 http.Client,避免每次检测重建 transport。
|
||||
// 自定义 Transport 在 dial 时强制再次校验 IP,防止 DNS rebinding 绕过 validateEndpoint。
|
||||
var monitorHTTPClient = newSSRFSafeHTTPClient(monitorRequestTimeout)
|
||||
|
||||
// monitorPingHTTPClient 用于 endpoint origin 的 HEAD ping,超时更短。
|
||||
var monitorPingHTTPClient = newSSRFSafeHTTPClient(monitorPingTimeout)
|
||||
|
||||
// newSSRFSafeHTTPClient 返回一个使用 safeDialContext 的 http.Client。
|
||||
// 仅供监控模块对外发起请求使用——所有目标都应是公网 endpoint。
|
||||
func newSSRFSafeHTTPClient(timeout time.Duration) *http.Client {
|
||||
tr := &http.Transport{
|
||||
DialContext: safeDialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 16,
|
||||
IdleConnTimeout: monitorIdleConnTimeout,
|
||||
TLSHandshakeTimeout: monitorTLSHandshakeTimeout,
|
||||
ResponseHeaderTimeout: monitorResponseHeaderTimeout,
|
||||
}
|
||||
return &http.Client{Timeout: timeout, Transport: tr}
|
||||
}
|
||||
|
||||
// runCheckForModel 对单个 (provider, model) 做一次完整检测。
|
||||
// 不返回 error:所有失败都包装进 CheckResult.Status=error/failed。
|
||||
func runCheckForModel(ctx context.Context, provider, endpoint, apiKey, model string) *CheckResult {
|
||||
res := &CheckResult{
|
||||
Model: model,
|
||||
Status: MonitorStatusError,
|
||||
CheckedAt: time.Now(),
|
||||
}
|
||||
|
||||
challenge := generateChallenge()
|
||||
|
||||
start := time.Now()
|
||||
respText, statusCode, err := callProvider(ctx, provider, endpoint, apiKey, model, challenge.Prompt)
|
||||
latency := time.Since(start)
|
||||
latencyMs := int(latency / time.Millisecond)
|
||||
res.LatencyMs = &latencyMs
|
||||
|
||||
if err != nil {
|
||||
res.Status = MonitorStatusError
|
||||
res.Message = truncateMessage(sanitizeErrorMessage(err.Error()))
|
||||
return res
|
||||
}
|
||||
if statusCode < 200 || statusCode >= 300 {
|
||||
res.Status = MonitorStatusError
|
||||
res.Message = truncateMessage(sanitizeErrorMessage(fmt.Sprintf("upstream HTTP %d: %s", statusCode, respText)))
|
||||
return res
|
||||
}
|
||||
|
||||
if !validateChallenge(respText, challenge.Expected) {
|
||||
res.Status = MonitorStatusFailed
|
||||
res.Message = truncateMessage(sanitizeErrorMessage(fmt.Sprintf("challenge mismatch (expected %s, got %q)", challenge.Expected, respText)))
|
||||
return res
|
||||
}
|
||||
|
||||
if latency >= monitorDegradedThreshold {
|
||||
res.Status = MonitorStatusDegraded
|
||||
res.Message = truncateMessage(fmt.Sprintf("slow response: %dms", latencyMs))
|
||||
return res
|
||||
}
|
||||
|
||||
res.Status = MonitorStatusOperational
|
||||
return res
|
||||
}
|
||||
|
||||
// pingEndpointOrigin 对 endpoint 的 origin (scheme://host) 发起 HEAD 请求,返回耗时。
|
||||
// 失败时返回 nil(不影响主状态判定)。
|
||||
func pingEndpointOrigin(ctx context.Context, endpoint string) *int {
|
||||
origin, err := extractOrigin(endpoint)
|
||||
if err != nil || origin == "" {
|
||||
return nil
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, origin, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
start := time.Now()
|
||||
resp, err := monitorPingHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, monitorPingDiscardMaxBytes))
|
||||
ms := int(time.Since(start) / time.Millisecond)
|
||||
return &ms
|
||||
}
|
||||
|
||||
// providerAdapter 描述某个 provider 在 challenge 检测中需要的 4 件事:
|
||||
// - 拼出请求路径(含 model 占位)
|
||||
// - 序列化请求体
|
||||
// - 构造鉴权头
|
||||
// - 从响应 JSON 中按 path 提取文本(gjson path)
|
||||
//
|
||||
// 加新 provider 只需要在 providerAdapters 里增加一个条目,无需触碰 callProvider / validateProvider。
|
||||
type providerAdapter struct {
|
||||
buildPath func(model string) string
|
||||
buildBody func(model, prompt string) ([]byte, error)
|
||||
buildHeaders func(apiKey string) map[string]string
|
||||
textPath string // gjson 提取响应文本的 path
|
||||
}
|
||||
|
||||
// providerAdapters 全部已支持的 provider。键值即 MonitorProvider* 字符串。
|
||||
//
|
||||
//nolint:gochecknoglobals // 适配器表是只读静态数据,初始化后不变更。
|
||||
var providerAdapters = map[string]providerAdapter{
|
||||
MonitorProviderOpenAI: {
|
||||
buildPath: func(string) string { return providerOpenAIPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"messages": []map[string]string{{"role": "user", "content": prompt}},
|
||||
"max_tokens": monitorChallengeMaxTokens,
|
||||
"stream": false,
|
||||
})
|
||||
},
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{"Authorization": "Bearer " + apiKey}
|
||||
},
|
||||
textPath: "choices.0.message.content",
|
||||
},
|
||||
MonitorProviderAnthropic: {
|
||||
buildPath: func(string) string { return providerAnthropicPath },
|
||||
buildBody: func(model, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"model": model,
|
||||
"messages": []map[string]string{{"role": "user", "content": prompt}},
|
||||
"max_tokens": monitorChallengeMaxTokens,
|
||||
})
|
||||
},
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{
|
||||
"x-api-key": apiKey,
|
||||
"anthropic-version": monitorAnthropicAPIVersion,
|
||||
}
|
||||
},
|
||||
textPath: "content.0.text",
|
||||
},
|
||||
MonitorProviderGemini: {
|
||||
// Gemini 把 model 名写在 URL path 上:/v1beta/models/{model}:generateContent
|
||||
buildPath: func(model string) string { return fmt.Sprintf(providerGeminiPathTemplate, model) },
|
||||
buildBody: func(_, prompt string) ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"contents": []map[string]any{
|
||||
{"parts": []map[string]any{{"text": prompt}}},
|
||||
},
|
||||
"generationConfig": map[string]any{"maxOutputTokens": monitorChallengeMaxTokens},
|
||||
})
|
||||
},
|
||||
// 使用 x-goog-api-key header 而不是 ?key= query,避免 *url.Error 把 key 回填到错误日志。
|
||||
buildHeaders: func(apiKey string) map[string]string {
|
||||
return map[string]string{"x-goog-api-key": apiKey}
|
||||
},
|
||||
textPath: "candidates.0.content.parts.0.text",
|
||||
},
|
||||
}
|
||||
|
||||
// isSupportedProvider 校验 provider 字符串是否在 adapter 表中。
|
||||
// 供 validate.go 的 validateProvider 复用,避免两份 switch 漂移。
|
||||
func isSupportedProvider(p string) bool {
|
||||
_, ok := providerAdapters[p]
|
||||
return ok
|
||||
}
|
||||
|
||||
// callProvider 通过 providerAdapters 分发到具体实现。
|
||||
// 返回值:响应中提取的文本、HTTP status、网络/序列化错误。
|
||||
func callProvider(ctx context.Context, provider, endpoint, apiKey, model, prompt string) (string, int, error) {
|
||||
adapter, ok := providerAdapters[provider]
|
||||
if !ok {
|
||||
return "", 0, fmt.Errorf("unsupported provider %q", provider)
|
||||
}
|
||||
body, err := adapter.buildBody(model, prompt)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("marshal body: %w", err)
|
||||
}
|
||||
full := joinURL(endpoint, adapter.buildPath(model))
|
||||
respBody, status, err := postRawJSON(ctx, full, body, adapter.buildHeaders(apiKey))
|
||||
if err != nil {
|
||||
return "", status, err
|
||||
}
|
||||
return gjson.GetBytes(respBody, adapter.textPath).String(), status, nil
|
||||
}
|
||||
|
||||
// postRawJSON 发送 POST + 已序列化好的 JSON 字节,限制响应体大小,返回响应字节、HTTP status、错误。
|
||||
// adapter 自行 marshal 是为了精确控制字段顺序与类型,所以这里直接收 []byte 而不是 any。
|
||||
func postRawJSON(ctx context.Context, fullURL string, payload []byte, headers map[string]string) ([]byte, int, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := monitorHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("do request: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
respBody, err := io.ReadAll(io.LimitReader(resp.Body, monitorResponseMaxBytes))
|
||||
if err != nil {
|
||||
return nil, resp.StatusCode, fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
return respBody, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
// joinURL 把 base origin 与 path 拼成完整 URL。
|
||||
// 容忍 base 末尾有/无斜杠,path 必带前导斜杠。
|
||||
func joinURL(base, path string) string {
|
||||
base = strings.TrimRight(base, "/")
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
return base + path
|
||||
}
|
||||
|
||||
// extractOrigin 从一个 endpoint URL 中提取 scheme://host[:port] 部分。
|
||||
func extractOrigin(endpoint string) (string, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Scheme == "" || u.Host == "" {
|
||||
return "", errors.New("endpoint missing scheme or host")
|
||||
}
|
||||
return u.Scheme + "://" + u.Host, nil
|
||||
}
|
||||
|
||||
// monitorSensitiveQueryParamRegex 匹配 URL query 中可能泄露凭证的参数:
|
||||
// key / api_key / api-key / access_token / token / authorization / x-api-key。
|
||||
// 大小写不敏感,匹配 `?name=value` 或 `&name=value` 形式(value 截到 & 或字符串末尾)。
|
||||
var monitorSensitiveQueryParamRegex = regexp.MustCompile(`(?i)([?&](?:key|api[_-]?key|access[_-]?token|token|authorization|x-api-key)=)[^&\s"']+`)
|
||||
|
||||
// monitorAPIKeyPatterns 匹配常见 provider 的 API key 字面量。
|
||||
// 顺序敏感:sk-ant- 必须放在 sk- 之前,否则会被通用 sk- 模式先消费。
|
||||
var monitorAPIKeyPatterns = []struct {
|
||||
pattern *regexp.Regexp
|
||||
replace string
|
||||
}{
|
||||
// Anthropic(带前缀,必须先匹配):sk-ant-xxxxxxx
|
||||
{regexp.MustCompile(`sk-ant-[A-Za-z0-9_-]{20,}`), "sk-ant-***REDACTED***"},
|
||||
// OpenAI / Anthropic 通用 sk-: sk-xxxxxxx
|
||||
{regexp.MustCompile(`sk-[A-Za-z0-9-]{20,}`), "sk-***REDACTED***"},
|
||||
// Gemini / Google API Key:固定前缀 + 35 位
|
||||
{regexp.MustCompile(`AIza[A-Za-z0-9_-]{35}`), "AIza***REDACTED***"},
|
||||
// JWT 三段式(Bearer 后常出现):eyJxxx.eyJxxx.signature
|
||||
{regexp.MustCompile(`eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}`), "eyJ***REDACTED.JWT***"},
|
||||
}
|
||||
|
||||
// sanitizeErrorMessage 擦除错误/响应文本中可能泄露的 API key。
|
||||
// 处理两类来源:
|
||||
// 1. URL query 中的 ?key= / ?api_key= 等(Go *url.Error 会回填完整 URL)
|
||||
// 2. 上游 HTTP body 文本里直接出现的 sk-* / AIza* / JWT 等密钥碎片
|
||||
//
|
||||
// 注意:与 gemini_messages_compat_service.go 的 sanitizeUpstreamErrorMessage 关注点类似但参数集更广,
|
||||
// 监控模块独立维护,避免互相耦合。
|
||||
func sanitizeErrorMessage(msg string) string {
|
||||
if msg == "" {
|
||||
return msg
|
||||
}
|
||||
msg = monitorSensitiveQueryParamRegex.ReplaceAllString(msg, `${1}REDACTED`)
|
||||
for _, p := range monitorAPIKeyPatterns {
|
||||
msg = p.pattern.ReplaceAllString(msg, p.replace)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// truncateMessage 把消息按 monitorMessageMaxBytes 截断,避免 DB 列溢出与日志过长。
|
||||
func truncateMessage(msg string) string {
|
||||
if len(msg) <= monitorMessageMaxBytes {
|
||||
return msg
|
||||
}
|
||||
const ellipsis = "...(truncated)"
|
||||
cutoff := monitorMessageMaxBytes - len(ellipsis)
|
||||
if cutoff < 0 {
|
||||
cutoff = 0
|
||||
}
|
||||
return msg[:cutoff] + ellipsis
|
||||
}
|
||||
137
backend/internal/service/channel_monitor_const.go
Normal file
137
backend/internal/service/channel_monitor_const.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// ChannelMonitor 全局常量。
|
||||
// 这些是 MVP 阶段的硬编码值,按需可以提到 config 中。
|
||||
const (
|
||||
// monitorRequestTimeout 单次模型请求总超时(含 Body 读取)。
|
||||
monitorRequestTimeout = 45 * time.Second
|
||||
// monitorPingTimeout HEAD 请求 endpoint origin 的超时。
|
||||
monitorPingTimeout = 8 * time.Second
|
||||
// monitorDegradedThreshold 主请求成功但耗时超过该阈值视为 degraded。
|
||||
monitorDegradedThreshold = 6 * time.Second
|
||||
// monitorHistoryRetentionDays 历史保留天数(每天清理一次)。
|
||||
monitorHistoryRetentionDays = 30
|
||||
// monitorWorkerConcurrency 调度器并发执行的监控数(pond 池容量)。
|
||||
monitorWorkerConcurrency = 5
|
||||
// monitorTickerInterval 调度器扫描"到期监控"的间隔。
|
||||
monitorTickerInterval = 5 * time.Second
|
||||
// monitorMinIntervalSeconds / monitorMaxIntervalSeconds 用户配置的检测间隔上下限。
|
||||
monitorMinIntervalSeconds = 15
|
||||
monitorMaxIntervalSeconds = 3600
|
||||
// monitorMessageMaxBytes message 字段最大字节数(与 schema/migration 一致)。
|
||||
monitorMessageMaxBytes = 500
|
||||
// monitorResponseMaxBytes 单次模型响应最大读取字节,防止 OOM。
|
||||
monitorResponseMaxBytes = 64 * 1024
|
||||
// monitorChallengeMin / monitorChallengeMax challenge 操作数范围。
|
||||
monitorChallengeMin = 1
|
||||
monitorChallengeMax = 50
|
||||
|
||||
// providerOpenAIPath OpenAI Chat Completions 路径。
|
||||
providerOpenAIPath = "/v1/chat/completions"
|
||||
// providerAnthropicPath Anthropic Messages 路径。
|
||||
providerAnthropicPath = "/v1/messages"
|
||||
// providerGeminiPathTemplate Gemini generateContent 路径模板(含 model 占位)。
|
||||
providerGeminiPathTemplate = "/v1beta/models/%s:generateContent"
|
||||
|
||||
// MonitorProviderOpenAI / Anthropic / Gemini provider 字符串常量(也是 ent enum 的实际值)。
|
||||
MonitorProviderOpenAI = "openai"
|
||||
MonitorProviderAnthropic = "anthropic"
|
||||
MonitorProviderGemini = "gemini"
|
||||
|
||||
// MonitorStatusOperational 等监控状态字符串常量(与 ent enum 一致)。
|
||||
MonitorStatusOperational = "operational"
|
||||
MonitorStatusDegraded = "degraded"
|
||||
MonitorStatusFailed = "failed"
|
||||
MonitorStatusError = "error"
|
||||
|
||||
// monitorAvailability7Days / 15 / 30 用于聚合查询窗口。
|
||||
monitorAvailability7Days = 7
|
||||
monitorAvailability15Days = 15
|
||||
monitorAvailability30Days = 30
|
||||
|
||||
// monitorCleanupCheckInterval 历史清理调度器的检查频率(每小时检查"是否到 03:00")。
|
||||
monitorCleanupCheckInterval = time.Hour
|
||||
// monitorCleanupHour 凌晨 3 点执行历史清理。
|
||||
monitorCleanupHour = 3
|
||||
|
||||
// MonitorHistoryDefaultLimit 历史查询默认返回条数(handler 层共享)。
|
||||
MonitorHistoryDefaultLimit = 100
|
||||
// MonitorHistoryMaxLimit 历史查询最大返回条数(handler 层共享)。
|
||||
MonitorHistoryMaxLimit = 1000
|
||||
|
||||
// monitorEndpointResolveTimeout validateEndpoint 解析 hostname 的最长耗时。
|
||||
monitorEndpointResolveTimeout = 5 * time.Second
|
||||
|
||||
// ---- checker / runner 行为参数(消除 magic 值)----
|
||||
|
||||
// monitorAnthropicAPIVersion Anthropic Messages API 版本头。
|
||||
monitorAnthropicAPIVersion = "2023-06-01"
|
||||
// monitorChallengeMaxTokens 单次 challenge 请求的 max_tokens(足够回答个位数算术)。
|
||||
monitorChallengeMaxTokens = 50
|
||||
|
||||
// monitorListDueTimeout tickDueChecks 查询到期监控的总超时。
|
||||
monitorListDueTimeout = 10 * time.Second
|
||||
// monitorRunOneBuffer runOne 的总超时缓冲(除请求超时与 ping 超时外的额外裕量)。
|
||||
monitorRunOneBuffer = 10 * time.Second
|
||||
// monitorCleanupTimeout 历史清理任务的总超时。
|
||||
monitorCleanupTimeout = 30 * time.Second
|
||||
// monitorCleanupDayLayout 历史清理用于"今日是否已跑过"判定的日期格式。
|
||||
monitorCleanupDayLayout = "2006-01-02"
|
||||
|
||||
// monitorIdleConnTimeout HTTP transport 空闲连接关闭超时。
|
||||
monitorIdleConnTimeout = 30 * time.Second
|
||||
// monitorTLSHandshakeTimeout HTTP transport TLS 握手超时。
|
||||
monitorTLSHandshakeTimeout = 10 * time.Second
|
||||
// monitorResponseHeaderTimeout HTTP transport 等待响应头超时。
|
||||
monitorResponseHeaderTimeout = 30 * time.Second
|
||||
// monitorPingDiscardMaxBytes ping 时丢弃响应体的最大字节数。
|
||||
monitorPingDiscardMaxBytes = 1024
|
||||
|
||||
// monitorDialTimeout 自定义 dialer 单次连接超时。
|
||||
monitorDialTimeout = 10 * time.Second
|
||||
// monitorDialKeepAlive 自定义 dialer keep-alive 间隔。
|
||||
monitorDialKeepAlive = 30 * time.Second
|
||||
)
|
||||
|
||||
// 业务错误(统一在此声明,避免散落)。
|
||||
var (
|
||||
ErrChannelMonitorNotFound = infraerrors.NotFound(
|
||||
"CHANNEL_MONITOR_NOT_FOUND", "channel monitor not found",
|
||||
)
|
||||
ErrChannelMonitorInvalidProvider = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_PROVIDER", "provider must be one of openai/anthropic/gemini",
|
||||
)
|
||||
ErrChannelMonitorInvalidInterval = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_INTERVAL", "interval_seconds must be in [15, 3600]",
|
||||
)
|
||||
ErrChannelMonitorInvalidEndpoint = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_ENDPOINT", "endpoint must be a valid https URL",
|
||||
)
|
||||
ErrChannelMonitorEndpointScheme = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_ENDPOINT_SCHEME", "endpoint must use https scheme",
|
||||
)
|
||||
ErrChannelMonitorEndpointPath = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_ENDPOINT_PATH", "endpoint must be base origin only (no path/query/fragment)",
|
||||
)
|
||||
ErrChannelMonitorEndpointPrivate = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_ENDPOINT_PRIVATE", "endpoint must be a public host",
|
||||
)
|
||||
ErrChannelMonitorEndpointUnreachable = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_ENDPOINT_UNREACHABLE", "endpoint hostname could not be resolved",
|
||||
)
|
||||
ErrChannelMonitorMissingAPIKey = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_MISSING_API_KEY", "api_key is required when creating a monitor",
|
||||
)
|
||||
ErrChannelMonitorMissingPrimaryModel = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_MISSING_PRIMARY_MODEL", "primary_model is required",
|
||||
)
|
||||
ErrChannelMonitorAPIKeyDecryptFailed = infraerrors.InternalServer(
|
||||
"CHANNEL_MONITOR_KEY_DECRYPT_FAILED", "api key decryption failed; please re-edit the monitor with a fresh key",
|
||||
)
|
||||
)
|
||||
208
backend/internal/service/channel_monitor_runner.go
Normal file
208
backend/internal/service/channel_monitor_runner.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alitto/pond/v2"
|
||||
)
|
||||
|
||||
// ChannelMonitorRunner 渠道监控调度器。
|
||||
//
|
||||
// 职责:
|
||||
// - 每 monitorTickerInterval 扫描一次"到期需要检测"的监控
|
||||
// - 通过 pond 池(容量 monitorWorkerConcurrency)异步执行检测
|
||||
// - 每小时检查一次时钟,到 monitorCleanupHour 点时执行历史清理
|
||||
// - Stop 时优雅关闭:池 drain + ticker.Stop + wg.Wait
|
||||
//
|
||||
// 不引入 cron 库;清理调度通过"每小时检查时间"实现,足够 MVP。
|
||||
type ChannelMonitorRunner struct {
|
||||
svc *ChannelMonitorService
|
||||
|
||||
pool pond.Pool
|
||||
stopCh chan struct{}
|
||||
once sync.Once
|
||||
wg sync.WaitGroup
|
||||
|
||||
// inFlight 跟踪正在执行的 monitor.ID。tickDueChecks 调度前会检查避免重复提交,
|
||||
// 防止单次检测耗时 > interval 时同一 monitor 被并发执行。
|
||||
inFlight map[int64]struct{}
|
||||
inFlightMu sync.Mutex
|
||||
|
||||
// 清理状态:lastCleanupDay 记录上次清理的"年-月-日",避免同一天重复跑。
|
||||
lastCleanupDay string
|
||||
cleanupMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewChannelMonitorRunner 构造调度器。Start 在 wire 中调用。
|
||||
func NewChannelMonitorRunner(svc *ChannelMonitorService) *ChannelMonitorRunner {
|
||||
return &ChannelMonitorRunner{
|
||||
svc: svc,
|
||||
stopCh: make(chan struct{}),
|
||||
inFlight: make(map[int64]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动 ticker + worker pool + cleanup loop。
|
||||
// 调用方需保证只调一次(wire ProvideChannelMonitorRunner 内只调一次)。
|
||||
func (r *ChannelMonitorRunner) Start() {
|
||||
if r == nil || r.svc == nil {
|
||||
return
|
||||
}
|
||||
// 容量 5 的 pond 池:超出时调用方等待,避免调度堆积无限增长。
|
||||
r.pool = pond.NewPool(monitorWorkerConcurrency)
|
||||
|
||||
r.wg.Add(2)
|
||||
go r.dueCheckLoop()
|
||||
go r.cleanupLoop()
|
||||
}
|
||||
|
||||
// Stop 优雅停止:close stopCh -> 等待两个 loop 退出 -> 池 drain。
|
||||
func (r *ChannelMonitorRunner) Stop() {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
r.once.Do(func() {
|
||||
close(r.stopCh)
|
||||
})
|
||||
r.wg.Wait()
|
||||
if r.pool != nil {
|
||||
r.pool.StopAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
// dueCheckLoop 每 monitorTickerInterval 扫描一次"到期监控",提交到池。
|
||||
func (r *ChannelMonitorRunner) dueCheckLoop() {
|
||||
defer r.wg.Done()
|
||||
|
||||
ticker := time.NewTicker(monitorTickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-r.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.tickDueChecks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tickDueChecks 一次扫描:查询到期监控并逐个提交到池。
|
||||
// 已在执行的 monitor 会被跳过(防止单次检测耗时 > interval 时重复调度)。
|
||||
// 池满时使用 TrySubmit 跳过(不能阻塞 ticker),同时立即释放已占用的 inFlight 槽。
|
||||
func (r *ChannelMonitorRunner) tickDueChecks() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), monitorListDueTimeout)
|
||||
defer cancel()
|
||||
|
||||
due, err := r.svc.listDueForCheck(ctx)
|
||||
if err != nil {
|
||||
slog.Warn("channel_monitor: list due failed", "error", err)
|
||||
return
|
||||
}
|
||||
for _, m := range due {
|
||||
monitor := m
|
||||
if !r.tryAcquireInFlight(monitor.ID) {
|
||||
slog.Debug("channel_monitor: skip already in-flight",
|
||||
"monitor_id", monitor.ID, "name", monitor.Name)
|
||||
continue
|
||||
}
|
||||
if _, ok := r.pool.TrySubmit(func() {
|
||||
r.runOne(monitor.ID, monitor.Name)
|
||||
}); !ok {
|
||||
// 池满:丢弃本次检测,但必须释放已占用的 inFlight 槽,否则该 monitor 会被永久卡住。
|
||||
r.releaseInFlight(monitor.ID)
|
||||
slog.Warn("channel_monitor: worker pool full, skip submission",
|
||||
"monitor_id", monitor.ID, "name", monitor.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tryAcquireInFlight 原子地占用 monitor 的 in-flight 槽。
|
||||
// 已被占用返回 false(调用方应跳过本次提交)。
|
||||
func (r *ChannelMonitorRunner) tryAcquireInFlight(id int64) bool {
|
||||
r.inFlightMu.Lock()
|
||||
defer r.inFlightMu.Unlock()
|
||||
if _, exists := r.inFlight[id]; exists {
|
||||
return false
|
||||
}
|
||||
r.inFlight[id] = struct{}{}
|
||||
return true
|
||||
}
|
||||
|
||||
// releaseInFlight 释放 in-flight 槽。runOne 完成(含 panic recover)后必须调用。
|
||||
func (r *ChannelMonitorRunner) releaseInFlight(id int64) {
|
||||
r.inFlightMu.Lock()
|
||||
delete(r.inFlight, id)
|
||||
r.inFlightMu.Unlock()
|
||||
}
|
||||
|
||||
// runOne 执行单个监控的检测。所有错误只记日志,不熔断。
|
||||
// 任务结束时(含 panic recover)必须释放 in-flight 槽。
|
||||
//
|
||||
// 单次解密路径:调 RunCheckByID,内部统一 Get + APIKeyDecryptFailed 判定 + 跑检测,
|
||||
// 避免 runner 自己再 Get 一次造成密文二次解密。
|
||||
func (r *ChannelMonitorRunner) runOne(id int64, name string) {
|
||||
// 单次任务上限 = 请求超时 + ping + 一些缓冲。
|
||||
ctx, cancel := context.WithTimeout(context.Background(), monitorRequestTimeout+monitorPingTimeout+monitorRunOneBuffer)
|
||||
defer cancel()
|
||||
|
||||
defer r.releaseInFlight(id)
|
||||
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
slog.Error("channel_monitor: runner panic",
|
||||
"monitor_id", id, "name", name, "panic", rec)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := r.svc.RunCheck(ctx, id); err != nil {
|
||||
// ErrChannelMonitorAPIKeyDecryptFailed 是预期可恢复错误,降为 Warn 即可。
|
||||
slog.Warn("channel_monitor: run check failed",
|
||||
"monitor_id", id, "name", name, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupLoop 每小时检查当前时间,到 monitorCleanupHour 点(且当天还没清理过)则跑一次清理。
|
||||
// 启动时立即检查一次,避免长时间运行才跑首次清理。
|
||||
func (r *ChannelMonitorRunner) cleanupLoop() {
|
||||
defer r.wg.Done()
|
||||
|
||||
ticker := time.NewTicker(monitorCleanupCheckInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
r.maybeRunCleanup()
|
||||
for {
|
||||
select {
|
||||
case <-r.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.maybeRunCleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// maybeRunCleanup 如果当前小时是 monitorCleanupHour 且当天未跑过,则执行清理。
|
||||
func (r *ChannelMonitorRunner) maybeRunCleanup() {
|
||||
now := time.Now()
|
||||
if now.Hour() != monitorCleanupHour {
|
||||
return
|
||||
}
|
||||
day := now.Format(monitorCleanupDayLayout)
|
||||
|
||||
r.cleanupMu.Lock()
|
||||
if r.lastCleanupDay == day {
|
||||
r.cleanupMu.Unlock()
|
||||
return
|
||||
}
|
||||
r.lastCleanupDay = day
|
||||
r.cleanupMu.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), monitorCleanupTimeout)
|
||||
defer cancel()
|
||||
if err := r.svc.cleanupOldHistory(ctx); err != nil {
|
||||
slog.Warn("channel_monitor: cleanup history failed", "error", err)
|
||||
}
|
||||
}
|
||||
374
backend/internal/service/channel_monitor_service.go
Normal file
374
backend/internal/service/channel_monitor_service.go
Normal file
@@ -0,0 +1,374 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ChannelMonitorRepository 渠道监控数据访问接口。
|
||||
// 入参/返回的指针类型均使用 service 包的 ChannelMonitor 模型,
|
||||
// repository 实现负责与 ent 模型互转,并保持 api_key_encrypted 字段为密文。
|
||||
type ChannelMonitorRepository interface {
|
||||
// CRUD
|
||||
Create(ctx context.Context, m *ChannelMonitor) error
|
||||
GetByID(ctx context.Context, id int64) (*ChannelMonitor, error)
|
||||
Update(ctx context.Context, m *ChannelMonitor) error
|
||||
Delete(ctx context.Context, id int64) error
|
||||
List(ctx context.Context, params ChannelMonitorListParams) ([]*ChannelMonitor, int64, error)
|
||||
|
||||
// 调度器辅助
|
||||
ListEnabled(ctx context.Context) ([]*ChannelMonitor, error)
|
||||
MarkChecked(ctx context.Context, id int64, checkedAt time.Time) error
|
||||
InsertHistoryBatch(ctx context.Context, rows []*ChannelMonitorHistoryRow) error
|
||||
DeleteHistoryBefore(ctx context.Context, before time.Time) (int64, error)
|
||||
|
||||
// 历史记录
|
||||
ListHistory(ctx context.Context, monitorID int64, model string, limit int) ([]*ChannelMonitorHistoryEntry, error)
|
||||
|
||||
// 用户视图聚合
|
||||
ListLatestPerModel(ctx context.Context, monitorID int64) ([]*ChannelMonitorLatest, error)
|
||||
ComputeAvailability(ctx context.Context, monitorID int64, windowDays int) ([]*ChannelMonitorAvailability, error)
|
||||
|
||||
// 批量聚合(admin/user list 用,避免 N+1)
|
||||
ListLatestForMonitorIDs(ctx context.Context, ids []int64) (map[int64][]*ChannelMonitorLatest, error)
|
||||
ComputeAvailabilityForMonitors(ctx context.Context, ids []int64, windowDays int) (map[int64][]*ChannelMonitorAvailability, error)
|
||||
}
|
||||
|
||||
// ChannelMonitorService 渠道监控管理服务。
|
||||
type ChannelMonitorService struct {
|
||||
repo ChannelMonitorRepository
|
||||
encryptor SecretEncryptor
|
||||
}
|
||||
|
||||
// NewChannelMonitorService 创建渠道监控服务实例。
|
||||
func NewChannelMonitorService(repo ChannelMonitorRepository, encryptor SecretEncryptor) *ChannelMonitorService {
|
||||
return &ChannelMonitorService{repo: repo, encryptor: encryptor}
|
||||
}
|
||||
|
||||
// ---------- CRUD ----------
|
||||
|
||||
// List 列表查询(支持 provider/enabled/search 过滤 + 分页)。
|
||||
// 返回的 ChannelMonitor.APIKey 已解密为明文,handler 层负责脱敏。
|
||||
func (s *ChannelMonitorService) List(ctx context.Context, params ChannelMonitorListParams) ([]*ChannelMonitor, int64, error) {
|
||||
if params.Page < 1 {
|
||||
params.Page = 1
|
||||
}
|
||||
if params.PageSize < 1 || params.PageSize > 200 {
|
||||
params.PageSize = 20
|
||||
}
|
||||
items, total, err := s.repo.List(ctx, params)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("list channel monitors: %w", err)
|
||||
}
|
||||
for _, it := range items {
|
||||
s.decryptInPlace(it)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// Get 查询单个监控(解密 API Key)。
|
||||
func (s *ChannelMonitorService) Get(ctx context.Context, id int64) (*ChannelMonitor, error) {
|
||||
m, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.decryptInPlace(m)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Create 创建监控(内部加密 api_key)。
|
||||
func (s *ChannelMonitorService) Create(ctx context.Context, p ChannelMonitorCreateParams) (*ChannelMonitor, error) {
|
||||
if err := validateCreateParams(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encrypted, err := s.encryptor.Encrypt(p.APIKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encrypt api key: %w", err)
|
||||
}
|
||||
m := &ChannelMonitor{
|
||||
Name: strings.TrimSpace(p.Name),
|
||||
Provider: p.Provider,
|
||||
Endpoint: normalizeEndpoint(p.Endpoint),
|
||||
APIKey: encrypted, // 注意:传入 repository 时该字段为密文
|
||||
PrimaryModel: strings.TrimSpace(p.PrimaryModel),
|
||||
ExtraModels: normalizeModels(p.ExtraModels),
|
||||
GroupName: strings.TrimSpace(p.GroupName),
|
||||
Enabled: p.Enabled,
|
||||
IntervalSeconds: p.IntervalSeconds,
|
||||
CreatedBy: p.CreatedBy,
|
||||
}
|
||||
if err := s.repo.Create(ctx, m); err != nil {
|
||||
return nil, fmt.Errorf("create channel monitor: %w", err)
|
||||
}
|
||||
// 不再调 s.Get 重走解密链:已知刚加密的明文,直接构造响应。
|
||||
// 这样可避免 SecretEncryptor 解密失败时 APIKey 被静默清空的问题(见 Fix 4)。
|
||||
m.APIKey = strings.TrimSpace(p.APIKey)
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// validateCreateParams 把 Create 入参的所有校验聚拢为一个函数,避免 Create 主体超过 30 行。
|
||||
func validateCreateParams(p ChannelMonitorCreateParams) error {
|
||||
if err := validateProvider(p.Provider); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateInterval(p.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateEndpoint(p.Endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(p.APIKey) == "" {
|
||||
return ErrChannelMonitorMissingAPIKey
|
||||
}
|
||||
if strings.TrimSpace(p.PrimaryModel) == "" {
|
||||
return ErrChannelMonitorMissingPrimaryModel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新监控。APIKey 字段:nil 或空字符串 = 不修改;非空 = 加密后覆盖。
|
||||
func (s *ChannelMonitorService) Update(ctx context.Context, id int64, p ChannelMonitorUpdateParams) (*ChannelMonitor, error) {
|
||||
existing, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := applyMonitorUpdate(existing, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newPlainAPIKey, apiKeyUpdated, err := s.applyAPIKeyUpdate(existing, p.APIKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.repo.Update(ctx, existing); err != nil {
|
||||
return nil, fmt.Errorf("update channel monitor: %w", err)
|
||||
}
|
||||
|
||||
// 不再调 s.Get 重走解密链:避免二次解密带来的"密文被静默清空"风险(与 Create 一致)。
|
||||
if apiKeyUpdated {
|
||||
existing.APIKey = newPlainAPIKey
|
||||
} else {
|
||||
s.decryptInPlace(existing)
|
||||
}
|
||||
return existing, nil
|
||||
}
|
||||
|
||||
// applyAPIKeyUpdate 处理 Update 中的 APIKey 字段:
|
||||
// - 入参 raw 为 nil 或空白:不修改 existing.APIKey(仍为密文),返回 updated=false
|
||||
// - 非空:加密后写入 existing.APIKey;同时把明文返回给调用方,
|
||||
// 供写库成功后塞回 existing 避免把密文吐回客户端
|
||||
func (s *ChannelMonitorService) applyAPIKeyUpdate(existing *ChannelMonitor, raw *string) (plain string, updated bool, err error) {
|
||||
if raw == nil || strings.TrimSpace(*raw) == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
plain = strings.TrimSpace(*raw)
|
||||
encrypted, encErr := s.encryptor.Encrypt(plain)
|
||||
if encErr != nil {
|
||||
return "", false, fmt.Errorf("encrypt api key: %w", encErr)
|
||||
}
|
||||
existing.APIKey = encrypted
|
||||
return plain, true, nil
|
||||
}
|
||||
|
||||
// Delete 删除监控(历史通过外键 CASCADE 自动清理)。
|
||||
func (s *ChannelMonitorService) Delete(ctx context.Context, id int64) error {
|
||||
if err := s.repo.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("delete channel monitor: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListHistory 列出某个监控最近的检测历史。
|
||||
// model 为空表示返回所有模型;limit <= 0 时使用默认值,超过上限会被截断。
|
||||
func (s *ChannelMonitorService) ListHistory(ctx context.Context, id int64, model string, limit int) ([]*ChannelMonitorHistoryEntry, error) {
|
||||
if _, err := s.repo.GetByID(ctx, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = MonitorHistoryDefaultLimit
|
||||
}
|
||||
if limit > MonitorHistoryMaxLimit {
|
||||
limit = MonitorHistoryMaxLimit
|
||||
}
|
||||
entries, err := s.repo.ListHistory(ctx, id, strings.TrimSpace(model), limit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list history: %w", err)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// ---------- 业务 ----------
|
||||
|
||||
// RunCheck 同步触发对一个监控的检测:并发跑 primary + extra 模型,
|
||||
// 写历史记录并更新 last_checked_at。返回每个模型的检测结果。
|
||||
func (s *ChannelMonitorService) RunCheck(ctx context.Context, id int64) ([]*CheckResult, error) {
|
||||
m, err := s.Get(ctx, id) // 已解密 APIKey
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.APIKeyDecryptFailed {
|
||||
return nil, ErrChannelMonitorAPIKeyDecryptFailed
|
||||
}
|
||||
results := s.runChecksConcurrent(ctx, m)
|
||||
s.persistCheckResults(ctx, m, results)
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// persistCheckResults 写入本次检测的历史记录并更新 last_checked_at。
|
||||
// 任一写库失败都只记日志,不影响调用方拿到 results(与 MVP 期望一致:宁可漏记历史也要先返回结果)。
|
||||
func (s *ChannelMonitorService) persistCheckResults(ctx context.Context, m *ChannelMonitor, results []*CheckResult) {
|
||||
rows := make([]*ChannelMonitorHistoryRow, 0, len(results))
|
||||
for _, r := range results {
|
||||
rows = append(rows, &ChannelMonitorHistoryRow{
|
||||
MonitorID: m.ID,
|
||||
Model: r.Model,
|
||||
Status: r.Status,
|
||||
LatencyMs: r.LatencyMs,
|
||||
PingLatencyMs: r.PingLatencyMs,
|
||||
Message: r.Message,
|
||||
CheckedAt: r.CheckedAt,
|
||||
})
|
||||
}
|
||||
if err := s.repo.InsertHistoryBatch(ctx, rows); err != nil {
|
||||
slog.Error("channel_monitor: insert history failed",
|
||||
"monitor_id", m.ID, "name", m.Name, "error", err)
|
||||
}
|
||||
if err := s.repo.MarkChecked(ctx, m.ID, time.Now()); err != nil {
|
||||
slog.Error("channel_monitor: mark checked failed",
|
||||
"monitor_id", m.ID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// runChecksConcurrent 对 primary + extra 模型并发执行检测。
|
||||
// errgroup 仅用于等待,不传播错误(每个 model 失败都已打包进 CheckResult)。
|
||||
func (s *ChannelMonitorService) runChecksConcurrent(ctx context.Context, m *ChannelMonitor) []*CheckResult {
|
||||
models := append([]string{m.PrimaryModel}, m.ExtraModels...)
|
||||
results := make([]*CheckResult, len(models))
|
||||
|
||||
// ping 共享一次,所有模型记录同一个 ping 延迟。
|
||||
pingMs := pingEndpointOrigin(ctx, m.Endpoint)
|
||||
|
||||
var eg errgroup.Group
|
||||
var mu sync.Mutex
|
||||
for i, model := range models {
|
||||
i, model := i, model
|
||||
eg.Go(func() error {
|
||||
r := runCheckForModel(ctx, m.Provider, m.Endpoint, m.APIKey, model)
|
||||
r.PingLatencyMs = pingMs
|
||||
mu.Lock()
|
||||
results[i] = r
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
_ = eg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// ---------- 调度器内部 ----------
|
||||
|
||||
// listDueForCheck 返回需要立即检测的监控列表:
|
||||
// enabled=true AND (last_checked_at IS NULL OR last_checked_at + interval <= now)。
|
||||
// 实现下沉到 repository(用 SQL 表达式比较),减少应用层数据传输。
|
||||
func (s *ChannelMonitorService) listDueForCheck(ctx context.Context) ([]*ChannelMonitor, error) {
|
||||
all, err := s.repo.ListEnabled(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := time.Now()
|
||||
due := make([]*ChannelMonitor, 0, len(all))
|
||||
for _, m := range all {
|
||||
if m.LastCheckedAt == nil {
|
||||
due = append(due, m)
|
||||
continue
|
||||
}
|
||||
nextAt := m.LastCheckedAt.Add(time.Duration(m.IntervalSeconds) * time.Second)
|
||||
if !nextAt.After(now) {
|
||||
due = append(due, m)
|
||||
}
|
||||
}
|
||||
return due, nil
|
||||
}
|
||||
|
||||
// cleanupOldHistory 删除 monitorHistoryRetentionDays 天之前的历史记录。
|
||||
func (s *ChannelMonitorService) cleanupOldHistory(ctx context.Context) error {
|
||||
before := time.Now().AddDate(0, 0, -monitorHistoryRetentionDays)
|
||||
deleted, err := s.repo.DeleteHistoryBefore(ctx, before)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete history before %s: %w", before.Format(time.RFC3339), err)
|
||||
}
|
||||
if deleted > 0 {
|
||||
slog.Info("channel_monitor: history cleanup",
|
||||
"deleted_rows", deleted, "before", before.Format(time.RFC3339))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
// decryptInPlace 把 ChannelMonitor.APIKey 从密文解密为明文。
|
||||
// 解密失败时把字段清空 + 设置 APIKeyDecryptFailed=true(不返回错误,避免阻断列表渲染)。
|
||||
// runner / RunCheck 必须读取该标志位并拒绝执行检测。
|
||||
func (s *ChannelMonitorService) decryptInPlace(m *ChannelMonitor) {
|
||||
if m == nil || m.APIKey == "" {
|
||||
return
|
||||
}
|
||||
plain, err := s.encryptor.Decrypt(m.APIKey)
|
||||
if err != nil {
|
||||
slog.Warn("channel_monitor: decrypt api key failed",
|
||||
"monitor_id", m.ID, "error", err)
|
||||
m.APIKey = ""
|
||||
m.APIKeyDecryptFailed = true
|
||||
return
|
||||
}
|
||||
m.APIKey = plain
|
||||
}
|
||||
|
||||
// applyMonitorUpdate 把 update params 中非 nil 的字段应用到 existing 上。
|
||||
// APIKey 字段在调用方单独处理(涉及加密)。
|
||||
//
|
||||
// 行数稍超过 30:这是逐字段平铺的 dispatcher,每个 if 都是 1-3 行的"非 nil 则覆盖"模式,
|
||||
// 拆分反而会增加跳转噪音、影响可读性,故保留为单函数。
|
||||
func applyMonitorUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams) error {
|
||||
if p.Name != nil {
|
||||
existing.Name = strings.TrimSpace(*p.Name)
|
||||
}
|
||||
if p.Provider != nil {
|
||||
if err := validateProvider(*p.Provider); err != nil {
|
||||
return err
|
||||
}
|
||||
existing.Provider = *p.Provider
|
||||
}
|
||||
if p.Endpoint != nil {
|
||||
if err := validateEndpoint(*p.Endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
existing.Endpoint = normalizeEndpoint(*p.Endpoint)
|
||||
}
|
||||
if p.PrimaryModel != nil {
|
||||
existing.PrimaryModel = strings.TrimSpace(*p.PrimaryModel)
|
||||
}
|
||||
if p.ExtraModels != nil {
|
||||
existing.ExtraModels = normalizeModels(*p.ExtraModels)
|
||||
}
|
||||
if p.GroupName != nil {
|
||||
existing.GroupName = strings.TrimSpace(*p.GroupName)
|
||||
}
|
||||
if p.Enabled != nil {
|
||||
existing.Enabled = *p.Enabled
|
||||
}
|
||||
if p.IntervalSeconds != nil {
|
||||
if err := validateInterval(*p.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
existing.IntervalSeconds = *p.IntervalSeconds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
152
backend/internal/service/channel_monitor_ssrf.go
Normal file
152
backend/internal/service/channel_monitor_ssrf.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SSRF 防护 helper:
|
||||
// - validateEndpoint 在 admin 提交时阻止 http/loopback/私网/云元数据 URL
|
||||
// - safeDialContext 在 socket 层再次校验真实 IP,防止 DNS rebinding
|
||||
//
|
||||
// 已知 cloud metadata hostname 拒绝列表(小写比较)。
|
||||
var monitorBlockedHostnames = map[string]struct{}{
|
||||
"localhost": {},
|
||||
"localhost.localdomain": {},
|
||||
"metadata": {},
|
||||
"metadata.google.internal": {},
|
||||
"metadata.goog": {},
|
||||
"instance-data": {},
|
||||
"instance-data.ec2.internal": {},
|
||||
}
|
||||
|
||||
// CIDR 列表:包含所有需要拒绝的 IPv4/IPv6 段。
|
||||
// 解析时只 panic 一次(启动时确认),生产路径只做 Contains。
|
||||
var monitorBlockedCIDRs = mustParseCIDRs([]string{
|
||||
"127.0.0.0/8", // IPv4 loopback
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
"169.254.0.0/16", // link-local(含云元数据 169.254.169.254)
|
||||
"100.64.0.0/10", // CGNAT
|
||||
"0.0.0.0/8", // "this network"
|
||||
"::1/128", // IPv6 loopback
|
||||
"fc00::/7", // IPv6 ULA
|
||||
"fe80::/10", // IPv6 link-local
|
||||
"::/128", // IPv6 unspecified
|
||||
})
|
||||
|
||||
// monitorDialer 共享 Dialer,与 net/http 默认值对齐。
|
||||
var monitorDialer = &net.Dialer{
|
||||
Timeout: monitorDialTimeout,
|
||||
KeepAlive: monitorDialKeepAlive,
|
||||
}
|
||||
|
||||
// mustParseCIDRs 在包初始化时解析 CIDR 字符串,失败 panic。
|
||||
func mustParseCIDRs(cidrs []string) []*net.IPNet {
|
||||
out := make([]*net.IPNet, 0, len(cidrs))
|
||||
for _, c := range cidrs {
|
||||
_, n, err := net.ParseCIDR(c)
|
||||
if err != nil {
|
||||
panic("channel_monitor_ssrf: invalid CIDR " + c + ": " + err.Error())
|
||||
}
|
||||
out = append(out, n)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// isBlockedHostname 判断 hostname 是否命中黑名单。
|
||||
func isBlockedHostname(hostname string) bool {
|
||||
if hostname == "" {
|
||||
return true
|
||||
}
|
||||
_, blocked := monitorBlockedHostnames[strings.ToLower(hostname)]
|
||||
return blocked
|
||||
}
|
||||
|
||||
// isPrivateIP 判断 IP 是否落在禁止段(loopback/RFC1918/link-local/ULA 等)。
|
||||
func isPrivateIP(ip net.IP) bool {
|
||||
if ip == nil {
|
||||
return true
|
||||
}
|
||||
if ip.IsUnspecified() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsInterfaceLocalMulticast() {
|
||||
return true
|
||||
}
|
||||
for _, n := range monitorBlockedCIDRs {
|
||||
if n.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isPrivateOrLoopbackHost 解析 hostname 的所有 A/AAAA 记录,
|
||||
// 任一 IP 落在私网/loopback 段即认为不安全。
|
||||
//
|
||||
// hostname 是 IP 字面量时也走同一路径。
|
||||
func isPrivateOrLoopbackHost(ctx context.Context, hostname string) (bool, error) {
|
||||
if isBlockedHostname(hostname) {
|
||||
return true, nil
|
||||
}
|
||||
// IP 字面量直接判断。
|
||||
if ip := net.ParseIP(hostname); ip != nil {
|
||||
return isPrivateIP(ip), nil
|
||||
}
|
||||
resolver := net.DefaultResolver
|
||||
addrs, err := resolver.LookupIPAddr(ctx, hostname)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if isPrivateIP(a.IP) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// safeDialContext 在真实 dial 前再次校验目标 IP,防止 DNS rebinding。
|
||||
// 解析 hostname 后逐个 IP 尝试连接,命中私网即拒绝(即便 validateEndpoint 时返回的是公网 IP)。
|
||||
func safeDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 字面量 IP 走快速路径。
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
if isPrivateIP(ip) {
|
||||
return nil, &net.AddrError{Err: "blocked by SSRF policy", Addr: address}
|
||||
}
|
||||
return monitorDialer.DialContext(ctx, network, address)
|
||||
}
|
||||
if isBlockedHostname(host) {
|
||||
return nil, &net.AddrError{Err: "blocked by SSRF policy", Addr: address}
|
||||
}
|
||||
addrs, err := net.DefaultResolver.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return nil, &net.AddrError{Err: "no addresses for host", Addr: host}
|
||||
}
|
||||
var lastErr error
|
||||
for _, a := range addrs {
|
||||
if isPrivateIP(a.IP) {
|
||||
lastErr = &net.AddrError{Err: "blocked by SSRF policy", Addr: a.IP.String()}
|
||||
continue
|
||||
}
|
||||
conn, err := monitorDialer.DialContext(ctx, network, net.JoinHostPort(a.IP.String(), port))
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
lastErr = err
|
||||
}
|
||||
if lastErr == nil {
|
||||
lastErr = &net.AddrError{Err: "no usable addresses", Addr: host}
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
161
backend/internal/service/channel_monitor_types.go
Normal file
161
backend/internal/service/channel_monitor_types.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package service
|
||||
|
||||
import "time"
|
||||
|
||||
// ChannelMonitor 渠道监控配置(service 层模型,不直接暴露 ent 类型)。
|
||||
type ChannelMonitor struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
Endpoint string
|
||||
APIKey string // 解密后的明文 API Key(仅在 service 内部使用,handler 层不应直接序列化返回)
|
||||
PrimaryModel string
|
||||
ExtraModels []string
|
||||
GroupName string
|
||||
Enabled bool
|
||||
IntervalSeconds int
|
||||
LastCheckedAt *time.Time
|
||||
CreatedBy int64
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
|
||||
// APIKeyDecryptFailed 表示 APIKey 字段无法解密(密钥不一致或损坏)。
|
||||
// 此时 APIKey 为空字符串,runner / RunCheck 必须跳过该监控并提示重填。
|
||||
APIKeyDecryptFailed bool
|
||||
}
|
||||
|
||||
// ChannelMonitorListParams 列表查询过滤参数。
|
||||
type ChannelMonitorListParams struct {
|
||||
Page int
|
||||
PageSize int
|
||||
Provider string
|
||||
Enabled *bool
|
||||
Search string
|
||||
}
|
||||
|
||||
// ChannelMonitorCreateParams 创建参数。
|
||||
type ChannelMonitorCreateParams struct {
|
||||
Name string
|
||||
Provider string
|
||||
Endpoint string
|
||||
APIKey string
|
||||
PrimaryModel string
|
||||
ExtraModels []string
|
||||
GroupName string
|
||||
Enabled bool
|
||||
IntervalSeconds int
|
||||
CreatedBy int64
|
||||
}
|
||||
|
||||
// ChannelMonitorUpdateParams 更新参数(指针字段表示"未提供则不更新")。
|
||||
type ChannelMonitorUpdateParams struct {
|
||||
Name *string
|
||||
Provider *string
|
||||
Endpoint *string
|
||||
APIKey *string // 空字符串表示不修改;非空字符串覆盖
|
||||
PrimaryModel *string
|
||||
ExtraModels *[]string
|
||||
GroupName *string
|
||||
Enabled *bool
|
||||
IntervalSeconds *int
|
||||
}
|
||||
|
||||
// CheckResult 单个模型一次检测的结果。
|
||||
type CheckResult struct {
|
||||
Model string
|
||||
Status string // operational / degraded / failed / error
|
||||
LatencyMs *int
|
||||
PingLatencyMs *int
|
||||
Message string
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// UserMonitorView 用户只读视图:监控概览(含主模型最近状态 + 7d 可用率 + 附加模型最近状态)。
|
||||
type UserMonitorView struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
GroupName string
|
||||
PrimaryModel string
|
||||
PrimaryStatus string
|
||||
PrimaryLatencyMs *int
|
||||
Availability7d float64 // 0-100
|
||||
ExtraModels []ExtraModelStatus
|
||||
}
|
||||
|
||||
// ExtraModelStatus 附加模型最近一次状态。
|
||||
type ExtraModelStatus struct {
|
||||
Model string
|
||||
Status string
|
||||
LatencyMs *int
|
||||
}
|
||||
|
||||
// UserMonitorDetail 用户只读视图:监控详情(含全部模型 7d/15d/30d 可用率与平均延迟)。
|
||||
type UserMonitorDetail struct {
|
||||
ID int64
|
||||
Name string
|
||||
Provider string
|
||||
GroupName string
|
||||
Models []ModelDetail
|
||||
}
|
||||
|
||||
// ModelDetail 单个模型的可用率/延迟统计。
|
||||
type ModelDetail struct {
|
||||
Model string
|
||||
LatestStatus string
|
||||
LatestLatencyMs *int
|
||||
Availability7d float64 // 0-100
|
||||
Availability15d float64
|
||||
Availability30d float64
|
||||
AvgLatency7dMs *int
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryRow 历史记录入库行(service 层向 repository 提交的数据)。
|
||||
type ChannelMonitorHistoryRow struct {
|
||||
MonitorID int64
|
||||
Model string
|
||||
Status string
|
||||
LatencyMs *int
|
||||
PingLatencyMs *int
|
||||
Message string
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// ChannelMonitorHistoryEntry 历史记录查询返回行(含 ent 主键 ID)。
|
||||
type ChannelMonitorHistoryEntry struct {
|
||||
ID int64
|
||||
Model string
|
||||
Status string
|
||||
LatencyMs *int
|
||||
PingLatencyMs *int
|
||||
Message string
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// ChannelMonitorLatest 最近一次检测的简明信息(用于 UserMonitorView 聚合)。
|
||||
type ChannelMonitorLatest struct {
|
||||
Model string
|
||||
Status string
|
||||
LatencyMs *int
|
||||
CheckedAt time.Time
|
||||
}
|
||||
|
||||
// ChannelMonitorAvailability 单个模型在某窗口内的可用率与平均延迟(用于 UserMonitorDetail 聚合)。
|
||||
type ChannelMonitorAvailability struct {
|
||||
Model string
|
||||
WindowDays int
|
||||
TotalChecks int
|
||||
OperationalChecks int // operational + degraded 视为可用
|
||||
AvailabilityPct float64
|
||||
AvgLatencyMs *int
|
||||
}
|
||||
|
||||
// MonitorStatusSummary 监控状态聚合(admin list 用,单次 repo 查询消除前端 N+1)。
|
||||
// PrimaryStatus / PrimaryLatencyMs 描述主模型最近状态;Availability7d 是主模型 7 天可用率;
|
||||
// ExtraModels 描述附加模型最近状态(用于 hover 展示)。
|
||||
type MonitorStatusSummary struct {
|
||||
PrimaryStatus string // 空字符串表示无历史
|
||||
PrimaryLatencyMs *int
|
||||
Availability7d float64 // 0-100,无历史时为 0
|
||||
ExtraModels []ExtraModelStatus
|
||||
}
|
||||
99
backend/internal/service/channel_monitor_validate.go
Normal file
99
backend/internal/service/channel_monitor_validate.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 渠道监控参数校验与归一化辅助函数。
|
||||
// 校验失败一律返回 channel_monitor_const.go 中预定义的 Err* 错误,错误信息不含具体 IP/hostname,避免泄露内网拓扑。
|
||||
|
||||
// validateProvider 校验 provider 字符串。
|
||||
// 唯一来源于 providerAdapters:新增 provider 只需要在 channel_monitor_checker.go 注册 adapter。
|
||||
func validateProvider(p string) error {
|
||||
if !isSupportedProvider(p) {
|
||||
return ErrChannelMonitorInvalidProvider
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateInterval 校验 interval_seconds 范围。
|
||||
func validateInterval(sec int) error {
|
||||
if sec < monitorMinIntervalSeconds || sec > monitorMaxIntervalSeconds {
|
||||
return ErrChannelMonitorInvalidInterval
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEndpoint 校验 endpoint:
|
||||
// - scheme 强制 https(拒绝 http,避免明文凭证 + 部分 SSRF 利用面)
|
||||
// - 必须为 origin(无 path/query/fragment),防止用户填 https://api.openai.com/v1
|
||||
// 导致 joinURL 拼出 /v1/v1/chat/completions
|
||||
// - hostname 不能是 localhost/metadata 等已知元数据 hostname
|
||||
// - 解析所有 IP,任一落在 loopback/RFC1918/link-local/ULA 段即拒绝(防 SSRF)
|
||||
//
|
||||
// 错误信息不暴露具体 IP / hostname,避免泄露内网拓扑。
|
||||
func validateEndpoint(ep string) error {
|
||||
ep = strings.TrimSpace(ep)
|
||||
if ep == "" {
|
||||
return ErrChannelMonitorInvalidEndpoint
|
||||
}
|
||||
u, err := url.Parse(ep)
|
||||
if err != nil {
|
||||
return ErrChannelMonitorInvalidEndpoint
|
||||
}
|
||||
if u.Scheme != "https" {
|
||||
return ErrChannelMonitorEndpointScheme
|
||||
}
|
||||
if u.Host == "" {
|
||||
return ErrChannelMonitorInvalidEndpoint
|
||||
}
|
||||
if u.Path != "" && u.Path != "/" {
|
||||
return ErrChannelMonitorEndpointPath
|
||||
}
|
||||
if u.RawQuery != "" || u.Fragment != "" {
|
||||
return ErrChannelMonitorEndpointPath
|
||||
}
|
||||
|
||||
hostname := u.Hostname()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), monitorEndpointResolveTimeout)
|
||||
defer cancel()
|
||||
blocked, err := isPrivateOrLoopbackHost(ctx, hostname)
|
||||
if err != nil {
|
||||
return ErrChannelMonitorEndpointUnreachable
|
||||
}
|
||||
if blocked {
|
||||
return ErrChannelMonitorEndpointPrivate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeEndpoint 去除前后空白与末尾 `/`,保证存储统一为 origin。
|
||||
// validateEndpoint 已确保格式合法(仅 origin),这里只做最终归一化。
|
||||
func normalizeEndpoint(ep string) string {
|
||||
ep = strings.TrimSpace(ep)
|
||||
ep = strings.TrimRight(ep, "/")
|
||||
return ep
|
||||
}
|
||||
|
||||
// normalizeModels 去除空白、重复模型名。保留输入顺序(map 的迭代顺序无关)。
|
||||
func normalizeModels(in []string) []string {
|
||||
if len(in) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
seen := make(map[string]struct{}, len(in))
|
||||
out := make([]string, 0, len(in))
|
||||
for _, m := range in {
|
||||
m = strings.TrimSpace(m)
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[m]; ok {
|
||||
continue
|
||||
}
|
||||
seen[m] = struct{}{}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -467,6 +467,8 @@ var ProviderSet = wire.NewSet(
|
||||
NewPaymentService,
|
||||
ProvidePaymentOrderExpiryService,
|
||||
ProvideBalanceNotifyService,
|
||||
ProvideChannelMonitorService,
|
||||
ProvideChannelMonitorRunner,
|
||||
)
|
||||
|
||||
// ProvidePaymentConfigService wraps NewPaymentConfigService to accept the named
|
||||
@@ -486,3 +488,20 @@ func ProvidePaymentOrderExpiryService(paymentSvc *PaymentService) *PaymentOrderE
|
||||
svc.Start()
|
||||
return svc
|
||||
}
|
||||
|
||||
// ProvideChannelMonitorService 创建渠道监控服务(CRUD + RunCheck + 用户视图聚合)。
|
||||
// 加密器复用 wire 中已注入的 SecretEncryptor(AES-256-GCM)。
|
||||
func ProvideChannelMonitorService(
|
||||
repo ChannelMonitorRepository,
|
||||
encryptor SecretEncryptor,
|
||||
) *ChannelMonitorService {
|
||||
return NewChannelMonitorService(repo, encryptor)
|
||||
}
|
||||
|
||||
// ProvideChannelMonitorRunner 创建并启动渠道监控调度器。
|
||||
// Runner.Stop 由 cleanup function 调用。
|
||||
func ProvideChannelMonitorRunner(svc *ChannelMonitorService) *ChannelMonitorRunner {
|
||||
r := NewChannelMonitorRunner(svc)
|
||||
r.Start()
|
||||
return r
|
||||
}
|
||||
|
||||
58
backend/migrations/125_add_channel_monitors.sql
Normal file
58
backend/migrations/125_add_channel_monitors.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- Migration: 125_add_channel_monitors
|
||||
-- 渠道监控 MVP:周期性对外部 provider/endpoint/api_key 做模型心跳测试。
|
||||
--
|
||||
-- 表结构说明:
|
||||
-- - channel_monitors 渠道配置表(一行 = 一个监控对象)
|
||||
-- - channel_monitor_histories 检测历史明细表(一次检测一个模型 = 一行)
|
||||
--
|
||||
-- 设计要点:
|
||||
-- - api_key_encrypted 列存放 AES-256-GCM 密文(base64),由 service 层加密。
|
||||
-- - extra_models 用 JSONB 存储字符串数组,便于扩展(后续可加权重等元数据)。
|
||||
-- - history 表通过 ON DELETE CASCADE 自动清理已删除监控的历史。
|
||||
-- - (enabled, last_checked_at) 索引服务于调度器扫描“到期需要检测”的监控。
|
||||
-- - histories 上 (monitor_id, model, checked_at DESC) 服务用户视图聚合查询;
|
||||
-- 单独的 (checked_at) 索引服务定期清理 30 天前数据的 DELETE。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_monitors (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
provider VARCHAR(20) NOT NULL, -- openai / anthropic / gemini
|
||||
endpoint VARCHAR(500) NOT NULL, -- base origin
|
||||
api_key_encrypted TEXT NOT NULL, -- AES-256-GCM (base64)
|
||||
primary_model VARCHAR(200) NOT NULL,
|
||||
extra_models JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
group_name VARCHAR(100) NOT NULL DEFAULT '',
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
interval_seconds INT NOT NULL,
|
||||
last_checked_at TIMESTAMPTZ,
|
||||
created_by BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT channel_monitors_provider_check CHECK (provider IN ('openai', 'anthropic', 'gemini')),
|
||||
CONSTRAINT channel_monitors_interval_check CHECK (interval_seconds BETWEEN 15 AND 3600)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitors_enabled_last_checked
|
||||
ON channel_monitors (enabled, last_checked_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitors_provider
|
||||
ON channel_monitors (provider);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitors_group_name
|
||||
ON channel_monitors (group_name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_monitor_histories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
monitor_id BIGINT NOT NULL REFERENCES channel_monitors(id) ON DELETE CASCADE,
|
||||
model VARCHAR(200) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
latency_ms INT,
|
||||
ping_latency_ms INT,
|
||||
message VARCHAR(500) NOT NULL DEFAULT '',
|
||||
checked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT channel_monitor_histories_status_check
|
||||
CHECK (status IN ('operational', 'degraded', 'failed', 'error'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitor_histories_monitor_model_checked
|
||||
ON channel_monitor_histories (monitor_id, model, checked_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_monitor_histories_checked_at
|
||||
ON channel_monitor_histories (checked_at);
|
||||
190
frontend/src/api/admin/channelMonitor.ts
Normal file
190
frontend/src/api/admin/channelMonitor.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Admin Channel Monitor API endpoints
|
||||
* Handles channel monitor (uptime/health) management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export type Provider = 'openai' | 'anthropic' | 'gemini'
|
||||
export type MonitorStatus = 'operational' | 'degraded' | 'failed' | 'error'
|
||||
|
||||
export interface ChannelMonitor {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
endpoint: string
|
||||
api_key_masked: string
|
||||
/**
|
||||
* True when the stored encrypted API key cannot be decrypted (e.g. the
|
||||
* encryption key has changed). Admin must re-edit the monitor to provide
|
||||
* a fresh key. Backend skips checks for these monitors.
|
||||
*/
|
||||
api_key_decrypt_failed?: boolean
|
||||
primary_model: string
|
||||
extra_models: string[]
|
||||
group_name: string
|
||||
enabled: boolean
|
||||
interval_seconds: number
|
||||
last_checked_at: string | null
|
||||
created_by: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
/** Latest status of the primary model (empty when no history yet) */
|
||||
primary_status: MonitorStatus | ''
|
||||
/** Latest latency of the primary model in ms (null when no history yet) */
|
||||
primary_latency_ms: number | null
|
||||
/** Primary model 7-day availability percentage (0-100) */
|
||||
availability_7d: number
|
||||
/** Latest status per extra model (used for hover tooltip) */
|
||||
extra_models_status: ExtraModelStatus[]
|
||||
}
|
||||
|
||||
export interface ExtraModelStatus {
|
||||
model: string
|
||||
status: MonitorStatus | ''
|
||||
latency_ms: number | null
|
||||
}
|
||||
|
||||
export interface ListParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
provider?: Provider
|
||||
enabled?: boolean
|
||||
search?: string
|
||||
}
|
||||
|
||||
export interface ListResponse {
|
||||
items: ChannelMonitor[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface CreateParams {
|
||||
name: string
|
||||
provider: Provider
|
||||
endpoint: string
|
||||
api_key: string
|
||||
primary_model: string
|
||||
extra_models?: string[]
|
||||
group_name?: string
|
||||
enabled?: boolean
|
||||
interval_seconds: number
|
||||
}
|
||||
|
||||
// Update request: api_key empty string means "do not modify"
|
||||
export type UpdateParams = Partial<CreateParams>
|
||||
|
||||
export interface CheckResult {
|
||||
model: string
|
||||
status: MonitorStatus
|
||||
latency_ms: number | null
|
||||
ping_latency_ms: number | null
|
||||
message: string
|
||||
checked_at: string
|
||||
}
|
||||
|
||||
export interface RunNowResponse {
|
||||
results: CheckResult[]
|
||||
}
|
||||
|
||||
export interface HistoryItem {
|
||||
id: number
|
||||
model: string
|
||||
status: MonitorStatus
|
||||
latency_ms: number | null
|
||||
ping_latency_ms: number | null
|
||||
message: string
|
||||
checked_at: string
|
||||
}
|
||||
|
||||
export interface HistoryParams {
|
||||
model?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface HistoryResponse {
|
||||
items: HistoryItem[]
|
||||
}
|
||||
|
||||
/**
|
||||
* List channel monitors with pagination and filters
|
||||
*/
|
||||
export async function list(
|
||||
params: ListParams = {},
|
||||
options?: { signal?: AbortSignal }
|
||||
): Promise<ListResponse> {
|
||||
const { data } = await apiClient.get<ListResponse>('/admin/channel-monitors', {
|
||||
params,
|
||||
signal: options?.signal,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a channel monitor by ID
|
||||
*/
|
||||
export async function get(id: number): Promise<ChannelMonitor> {
|
||||
const { data } = await apiClient.get<ChannelMonitor>(`/admin/channel-monitors/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new channel monitor
|
||||
*/
|
||||
export async function create(params: CreateParams): Promise<ChannelMonitor> {
|
||||
const { data } = await apiClient.post<ChannelMonitor>('/admin/channel-monitors', params)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing channel monitor.
|
||||
* api_key field: empty string means "do not modify".
|
||||
*/
|
||||
export async function update(id: number, params: UpdateParams): Promise<ChannelMonitor> {
|
||||
const { data } = await apiClient.put<ChannelMonitor>(`/admin/channel-monitors/${id}`, params)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a channel monitor
|
||||
*/
|
||||
export async function del(id: number): Promise<void> {
|
||||
await apiClient.delete(`/admin/channel-monitors/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an immediate manual check for a channel monitor.
|
||||
* Returns the latest check results for primary + extra models.
|
||||
*/
|
||||
export async function runNow(id: number): Promise<RunNowResponse> {
|
||||
const { data } = await apiClient.post<RunNowResponse>(`/admin/channel-monitors/${id}/run`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* List historical check results for a monitor.
|
||||
*/
|
||||
export async function listHistory(
|
||||
id: number,
|
||||
params: HistoryParams = {}
|
||||
): Promise<HistoryResponse> {
|
||||
const { data } = await apiClient.get<HistoryResponse>(
|
||||
`/admin/channel-monitors/${id}/history`,
|
||||
{ params }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const channelMonitorAPI = {
|
||||
list,
|
||||
get,
|
||||
create,
|
||||
update,
|
||||
del,
|
||||
runNow,
|
||||
listHistory,
|
||||
}
|
||||
|
||||
export default channelMonitorAPI
|
||||
@@ -26,6 +26,7 @@ import scheduledTestsAPI from './scheduledTests'
|
||||
import backupAPI from './backup'
|
||||
import tlsFingerprintProfileAPI from './tlsFingerprintProfile'
|
||||
import channelsAPI from './channels'
|
||||
import channelMonitorAPI from './channelMonitor'
|
||||
import adminPaymentAPI from './payment'
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,7 @@ export const adminAPI = {
|
||||
backup: backupAPI,
|
||||
tlsFingerprintProfiles: tlsFingerprintProfileAPI,
|
||||
channels: channelsAPI,
|
||||
channelMonitor: channelMonitorAPI,
|
||||
payment: adminPaymentAPI
|
||||
}
|
||||
|
||||
@@ -82,6 +84,7 @@ export {
|
||||
backupAPI,
|
||||
tlsFingerprintProfileAPI,
|
||||
channelsAPI,
|
||||
channelMonitorAPI,
|
||||
adminPaymentAPI
|
||||
}
|
||||
|
||||
|
||||
74
frontend/src/api/channelMonitor.ts
Normal file
74
frontend/src/api/channelMonitor.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* User-facing Channel Monitor API endpoints
|
||||
* Read-only views for end users to inspect channel availability/status.
|
||||
*/
|
||||
|
||||
import { apiClient } from './client'
|
||||
import type { Provider, MonitorStatus } from './admin/channelMonitor'
|
||||
|
||||
export type { Provider, MonitorStatus } from './admin/channelMonitor'
|
||||
|
||||
export interface UserMonitorExtraModel {
|
||||
model: string
|
||||
status: MonitorStatus
|
||||
latency_ms: number | null
|
||||
}
|
||||
|
||||
export interface UserMonitorView {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
group_name: string
|
||||
primary_model: string
|
||||
primary_status: MonitorStatus
|
||||
primary_latency_ms: number | null
|
||||
availability_7d: number
|
||||
extra_models: UserMonitorExtraModel[]
|
||||
}
|
||||
|
||||
export interface UserMonitorListResponse {
|
||||
items: UserMonitorView[]
|
||||
}
|
||||
|
||||
export interface UserMonitorModelDetail {
|
||||
model: string
|
||||
latest_status: MonitorStatus
|
||||
latest_latency_ms: number | null
|
||||
availability_7d: number
|
||||
availability_15d: number
|
||||
availability_30d: number
|
||||
avg_latency_7d_ms: number | null
|
||||
}
|
||||
|
||||
export interface UserMonitorDetail {
|
||||
id: number
|
||||
name: string
|
||||
provider: Provider
|
||||
group_name: string
|
||||
models: UserMonitorModelDetail[]
|
||||
}
|
||||
|
||||
/**
|
||||
* List all monitor views available to the current user.
|
||||
*/
|
||||
export async function list(options?: { signal?: AbortSignal }): Promise<UserMonitorListResponse> {
|
||||
const { data } = await apiClient.get<UserMonitorListResponse>('/channel-monitors', {
|
||||
signal: options?.signal,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed status (multi-window availability + latency) for a single monitor.
|
||||
*/
|
||||
export async function status(id: number): Promise<UserMonitorDetail> {
|
||||
const { data } = await apiClient.get<UserMonitorDetail>(`/channel-monitors/${id}/status`)
|
||||
return data
|
||||
}
|
||||
|
||||
export const channelMonitorUserAPI = {
|
||||
list,
|
||||
status,
|
||||
}
|
||||
|
||||
export default channelMonitorUserAPI
|
||||
@@ -18,6 +18,7 @@ export { paymentAPI } from './payment'
|
||||
export { userGroupsAPI } from './groups'
|
||||
export { totpAPI } from './totp'
|
||||
export { default as announcementsAPI } from './announcements'
|
||||
export { channelMonitorUserAPI } from './channelMonitor'
|
||||
|
||||
// Admin APIs
|
||||
export { adminAPI } from './admin'
|
||||
|
||||
45
frontend/src/components/admin/monitor/MonitorActionsCell.vue
Normal file
45
frontend/src/components/admin/monitor/MonitorActionsCell.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
@click="$emit('run', row)"
|
||||
:disabled="running"
|
||||
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
|
||||
>
|
||||
<Icon name="refresh" size="sm" :class="running ? 'animate-spin' : ''" />
|
||||
<span class="text-xs">{{ t('admin.channelMonitor.runNow') }}</span>
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('edit', row)"
|
||||
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
|
||||
>
|
||||
<Icon name="edit" size="sm" />
|
||||
<span class="text-xs">{{ t('common.edit') }}</span>
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('delete', row)"
|
||||
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
|
||||
>
|
||||
<Icon name="trash" size="sm" />
|
||||
<span class="text-xs">{{ t('common.delete') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ChannelMonitor } from '@/api/admin/channelMonitor'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
|
||||
defineProps<{
|
||||
row: ChannelMonitor
|
||||
running: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'run', row: ChannelMonitor): void
|
||||
(e: 'edit', row: ChannelMonitor): void
|
||||
(e: 'delete', row: ChannelMonitor): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
95
frontend/src/components/admin/monitor/MonitorFiltersBar.vue
Normal file
95
frontend/src/components/admin/monitor/MonitorFiltersBar.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="flex flex-col justify-between gap-4 lg:flex-row lg:items-start">
|
||||
<!-- Left: Search + Filters -->
|
||||
<div class="flex flex-1 flex-wrap items-center gap-3">
|
||||
<div class="relative w-full sm:w-64">
|
||||
<Icon
|
||||
name="search"
|
||||
size="md"
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
|
||||
/>
|
||||
<input
|
||||
v-model="search"
|
||||
type="text"
|
||||
:placeholder="t('admin.channelMonitor.searchPlaceholder')"
|
||||
class="input pl-10"
|
||||
@input="$emit('search-input')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
v-model="provider"
|
||||
:options="providerFilterOptions"
|
||||
:placeholder="t('admin.channelMonitor.allProviders')"
|
||||
class="w-44"
|
||||
@change="$emit('reload')"
|
||||
/>
|
||||
|
||||
<Select
|
||||
v-model="enabled"
|
||||
:options="enabledFilterOptions"
|
||||
:placeholder="t('admin.channelMonitor.enabledFilter')"
|
||||
class="w-40"
|
||||
@change="$emit('reload')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Right: Actions -->
|
||||
<div class="flex w-full flex-shrink-0 flex-wrap items-center justify-end gap-3 lg:w-auto">
|
||||
<button
|
||||
@click="$emit('reload')"
|
||||
:disabled="loading"
|
||||
class="btn btn-secondary"
|
||||
:title="t('common.refresh')"
|
||||
>
|
||||
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
|
||||
</button>
|
||||
<button @click="$emit('create')" class="btn btn-primary">
|
||||
<Icon name="plus" size="md" class="mr-2" />
|
||||
{{ t('admin.channelMonitor.createButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { Provider } from '@/api/admin/channelMonitor'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import {
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
defineProps<{
|
||||
loading: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'reload'): void
|
||||
(e: 'create'): void
|
||||
(e: 'search-input'): void
|
||||
}>()
|
||||
|
||||
const search = defineModel<string>('search', { required: true })
|
||||
const provider = defineModel<Provider | ''>('provider', { required: true })
|
||||
const enabled = defineModel<'' | 'true' | 'false'>('enabled', { required: true })
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const providerFilterOptions = computed(() => [
|
||||
{ value: '', label: t('admin.channelMonitor.allProviders') },
|
||||
{ value: PROVIDER_OPENAI, label: t('monitorCommon.providers.openai') },
|
||||
{ value: PROVIDER_ANTHROPIC, label: t('monitorCommon.providers.anthropic') },
|
||||
{ value: PROVIDER_GEMINI, label: t('monitorCommon.providers.gemini') },
|
||||
])
|
||||
|
||||
const enabledFilterOptions = computed(() => [
|
||||
{ value: '', label: t('admin.channelMonitor.allStatus') },
|
||||
{ value: 'true', label: t('admin.channelMonitor.onlyEnabled') },
|
||||
{ value: 'false', label: t('admin.channelMonitor.onlyDisabled') },
|
||||
])
|
||||
</script>
|
||||
297
frontend/src/components/admin/monitor/MonitorFormDialog.vue
Normal file
297
frontend/src/components/admin/monitor/MonitorFormDialog.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:show="show"
|
||||
:title="editing ? t('admin.channelMonitor.editTitle') : t('admin.channelMonitor.createTitle')"
|
||||
width="wide"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<form id="channel-monitor-form" @submit.prevent="handleSubmit" class="space-y-5">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.name') }} <span class="text-red-500">*</span></label>
|
||||
<input v-model="form.name" type="text" required class="input" :placeholder="t('admin.channelMonitor.form.namePlaceholder')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.provider') }} <span class="text-red-500">*</span></label>
|
||||
<Select v-model="form.provider" :options="providerOptions" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.endpoint') }} <span class="text-red-500">*</span></label>
|
||||
<div class="flex gap-2">
|
||||
<input v-model="form.endpoint" type="text" required class="input flex-1" :placeholder="t('admin.channelMonitor.form.endpointPlaceholder')" />
|
||||
<button type="button" @click="useCurrentDomain" class="btn btn-secondary whitespace-nowrap">
|
||||
{{ t('admin.channelMonitor.form.useCurrentDomain') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">
|
||||
{{ t('admin.channelMonitor.form.apiKey') }}<span v-if="!editing" class="text-red-500"> *</span>
|
||||
</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="form.api_key"
|
||||
type="password"
|
||||
:required="!editing"
|
||||
class="input flex-1"
|
||||
:placeholder="editing ? t('admin.channelMonitor.form.apiKeyEditPlaceholder') : t('admin.channelMonitor.form.apiKeyPlaceholder')"
|
||||
/>
|
||||
<button type="button" @click="openMyKeyPicker" class="btn btn-secondary whitespace-nowrap">
|
||||
{{ t('admin.channelMonitor.form.useMyKey') }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="editing && editing.api_key_masked" class="mt-1 text-xs text-gray-400">{{ editing.api_key_masked }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.primaryModel') }} <span class="text-red-500">*</span></label>
|
||||
<input v-model="form.primary_model" type="text" required class="input" :placeholder="t('admin.channelMonitor.form.primaryModelPlaceholder')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.extraModels') }}</label>
|
||||
<ModelTagInput
|
||||
:models="form.extra_models"
|
||||
:placeholder="t('admin.channelMonitor.form.extraModelsPlaceholder')"
|
||||
@update:models="form.extra_models = $event"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.groupName') }}</label>
|
||||
<input v-model="form.group_name" type="text" class="input" :placeholder="t('admin.channelMonitor.form.groupNamePlaceholder')" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.intervalSeconds') }} <span class="text-red-500">*</span></label>
|
||||
<input v-model.number="form.interval_seconds" type="number" min="15" max="3600" required class="input" />
|
||||
<p class="mt-1 text-xs text-gray-400">{{ t('admin.channelMonitor.form.intervalSecondsHint') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="input-label mb-0">{{ t('admin.channelMonitor.form.enabled') }}</label>
|
||||
<Toggle v-model="form.enabled" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button @click="$emit('close')" type="button" class="btn btn-secondary">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
form="channel-monitor-form"
|
||||
:disabled="submitting"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
{{ submitting
|
||||
? t('common.submitting')
|
||||
: editing ? t('common.update') : t('common.create') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
|
||||
<MonitorKeyPickerDialog
|
||||
:show="showKeyPicker"
|
||||
:loading="myKeysLoading"
|
||||
:keys="myActiveKeys"
|
||||
@close="showKeyPicker = false"
|
||||
@pick="pickMyKey"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import { keysAPI } from '@/api/keys'
|
||||
import type {
|
||||
ChannelMonitor,
|
||||
CreateParams,
|
||||
Provider,
|
||||
UpdateParams,
|
||||
} from '@/api/admin/channelMonitor'
|
||||
import type { ApiKey } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
import ModelTagInput from '@/components/admin/channel/ModelTagInput.vue'
|
||||
import MonitorKeyPickerDialog from '@/components/admin/monitor/MonitorKeyPickerDialog.vue'
|
||||
import {
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
DEFAULT_INTERVAL_SECONDS,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
monitor: ChannelMonitor | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void
|
||||
(e: 'saved'): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
|
||||
// editing is true when we have an existing monitor
|
||||
const editing = computed<ChannelMonitor | null>(() => props.monitor)
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
// API key picker
|
||||
const showKeyPicker = ref(false)
|
||||
const myKeysLoading = ref(false)
|
||||
const myActiveKeys = ref<ApiKey[]>([])
|
||||
|
||||
interface MonitorForm {
|
||||
name: string
|
||||
provider: Provider
|
||||
endpoint: string
|
||||
api_key: string
|
||||
primary_model: string
|
||||
extra_models: string[]
|
||||
group_name: string
|
||||
interval_seconds: number
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const form = reactive<MonitorForm>({
|
||||
name: '',
|
||||
provider: PROVIDER_OPENAI,
|
||||
endpoint: '',
|
||||
api_key: '',
|
||||
primary_model: '',
|
||||
extra_models: [],
|
||||
group_name: '',
|
||||
interval_seconds: DEFAULT_INTERVAL_SECONDS,
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
const providerOptions = computed(() => [
|
||||
{ value: PROVIDER_OPENAI, label: t('monitorCommon.providers.openai') },
|
||||
{ value: PROVIDER_ANTHROPIC, label: t('monitorCommon.providers.anthropic') },
|
||||
{ value: PROVIDER_GEMINI, label: t('monitorCommon.providers.gemini') },
|
||||
])
|
||||
|
||||
function resetForm() {
|
||||
form.name = ''
|
||||
form.provider = PROVIDER_OPENAI
|
||||
form.endpoint = ''
|
||||
form.api_key = ''
|
||||
form.primary_model = ''
|
||||
form.extra_models = []
|
||||
form.group_name = ''
|
||||
form.interval_seconds = DEFAULT_INTERVAL_SECONDS
|
||||
form.enabled = true
|
||||
}
|
||||
|
||||
function loadFromMonitor(m: ChannelMonitor) {
|
||||
form.name = m.name
|
||||
form.provider = m.provider
|
||||
form.endpoint = m.endpoint
|
||||
form.api_key = ''
|
||||
form.primary_model = m.primary_model
|
||||
form.extra_models = [...(m.extra_models || [])]
|
||||
form.group_name = m.group_name || ''
|
||||
form.interval_seconds = m.interval_seconds || DEFAULT_INTERVAL_SECONDS
|
||||
form.enabled = m.enabled
|
||||
}
|
||||
|
||||
// Re-sync form whenever the dialog is opened or the target monitor changes.
|
||||
watch(
|
||||
() => [props.show, props.monitor] as const,
|
||||
([show, m]) => {
|
||||
if (!show) return
|
||||
if (m) loadFromMonitor(m)
|
||||
else resetForm()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
function useCurrentDomain() {
|
||||
form.endpoint = window.location.origin
|
||||
}
|
||||
|
||||
async function openMyKeyPicker() {
|
||||
showKeyPicker.value = true
|
||||
if (myActiveKeys.value.length > 0) return
|
||||
myKeysLoading.value = true
|
||||
try {
|
||||
const res = await keysAPI.list(1, 100, { status: 'active' })
|
||||
const items = res.items || []
|
||||
const now = Date.now()
|
||||
myActiveKeys.value = items.filter(k => {
|
||||
if (k.status !== 'active') return false
|
||||
if (!k.expires_at) return true
|
||||
return new Date(k.expires_at).getTime() > now
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('admin.channelMonitor.form.noActiveKey')))
|
||||
} finally {
|
||||
myKeysLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function pickMyKey(k: ApiKey) {
|
||||
form.api_key = k.key
|
||||
showKeyPicker.value = false
|
||||
}
|
||||
|
||||
function buildPayload(): CreateParams {
|
||||
return {
|
||||
name: form.name.trim(),
|
||||
provider: form.provider,
|
||||
endpoint: form.endpoint.trim(),
|
||||
api_key: form.api_key.trim(),
|
||||
primary_model: form.primary_model.trim(),
|
||||
extra_models: form.extra_models,
|
||||
group_name: form.group_name.trim(),
|
||||
enabled: form.enabled,
|
||||
interval_seconds: form.interval_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (submitting.value) return
|
||||
if (!form.name.trim()) {
|
||||
appStore.showError(t('admin.channelMonitor.nameRequired'))
|
||||
return
|
||||
}
|
||||
if (!form.primary_model.trim()) {
|
||||
appStore.showError(t('admin.channelMonitor.primaryModelRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const target = editing.value
|
||||
if (target) {
|
||||
const { api_key, ...rest } = buildPayload()
|
||||
const req: UpdateParams = rest
|
||||
// Only send api_key if user typed a new value
|
||||
if (api_key) req.api_key = api_key
|
||||
await adminAPI.channelMonitor.update(target.id, req)
|
||||
appStore.showSuccess(t('admin.channelMonitor.updateSuccess'))
|
||||
} else {
|
||||
await adminAPI.channelMonitor.create(buildPayload())
|
||||
appStore.showSuccess(t('admin.channelMonitor.createSuccess'))
|
||||
}
|
||||
emit('saved')
|
||||
emit('close')
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('common.error')))
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:show="show"
|
||||
:title="t('admin.channelMonitor.form.selectKeyTitle')"
|
||||
width="normal"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.channelMonitor.form.selectKeyHint') }}
|
||||
</p>
|
||||
<div v-if="loading" class="py-6 text-center text-sm text-gray-500">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="keys.length === 0" class="py-6 text-center text-sm text-gray-500">
|
||||
{{ t('admin.channelMonitor.form.noActiveKey') }}
|
||||
</div>
|
||||
<div v-else class="max-h-72 space-y-1 overflow-auto">
|
||||
<button
|
||||
v-for="k in keys"
|
||||
:key="k.id"
|
||||
type="button"
|
||||
@click="$emit('pick', k)"
|
||||
class="block w-full rounded-lg border border-gray-200 px-3 py-2 text-left text-sm transition-colors hover:bg-gray-50 dark:border-dark-600 dark:hover:bg-dark-700"
|
||||
>
|
||||
<div class="font-medium text-gray-900 dark:text-white">{{ k.name }}</div>
|
||||
<div class="font-mono text-xs text-gray-500 dark:text-gray-400">{{ maskKey(k.key) }}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<button @click="$emit('close')" class="btn btn-secondary">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ApiKey } from '@/types'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
loading: boolean
|
||||
keys: ApiKey[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'close'): void
|
||||
(e: 'pick', key: ApiKey): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
function maskKey(key: string): string {
|
||||
if (!key) return ''
|
||||
if (key.length <= 12) return `${key.slice(0, 4)}***`
|
||||
return `${key.slice(0, 6)}...${key.slice(-4)}`
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">{{ row.primary_model }}</span>
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium"
|
||||
:class="statusBadgeClass(row.primary_status)"
|
||||
>
|
||||
{{ statusLabel(row.primary_status) }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs font-semibold text-gray-100">
|
||||
{{ row.primary_model }}
|
||||
<span
|
||||
class="ml-1 inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium"
|
||||
:class="statusBadgeClass(row.primary_status)"
|
||||
>
|
||||
{{ statusLabel(row.primary_status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="(row.extra_models?.length ?? 0) === 0" class="text-[11px] text-gray-300">
|
||||
{{ t('monitorCommon.extraModelsEmpty') }}
|
||||
</div>
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-400">
|
||||
{{ t('monitorCommon.extraModelsHeader') }}
|
||||
</div>
|
||||
<table class="w-full text-left text-[11px]">
|
||||
<thead>
|
||||
<tr class="text-gray-400">
|
||||
<th class="py-0.5 pr-2 font-medium">{{ t('admin.channelMonitor.columns.primaryModel') }}</th>
|
||||
<th class="py-0.5 pr-2 font-medium">{{ t('admin.channelMonitor.columns.actions') }}</th>
|
||||
<th class="py-0.5 font-medium">{{ t('admin.channelMonitor.columns.latency') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in (row.extra_models_status || [])" :key="m.model">
|
||||
<td class="py-0.5 pr-2 text-gray-100">{{ m.model }}</td>
|
||||
<td class="py-0.5 pr-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px]"
|
||||
:class="statusBadgeClass(m.status)"
|
||||
>
|
||||
{{ statusLabel(m.status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-0.5 text-gray-100">{{ formatLatency(m.latency_ms) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { ChannelMonitor } from '@/api/admin/channelMonitor'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
|
||||
defineProps<{
|
||||
row: ChannelMonitor
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { statusLabel, statusBadgeClass, formatLatency } = useChannelMonitorFormat()
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:show="show"
|
||||
:title="t('admin.channelMonitor.runResultTitle')"
|
||||
width="normal"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="r in results"
|
||||
:key="r.model"
|
||||
class="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2 text-sm dark:border-dark-600"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ r.model }}</span>
|
||||
<span v-if="r.message" class="text-xs text-gray-500 dark:text-gray-400">{{ r.message }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px]"
|
||||
:class="statusBadgeClass(r.status)"
|
||||
>
|
||||
{{ statusLabel(r.status) }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">{{ formatLatency(r.latency_ms) }} ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<button @click="$emit('close')" class="btn btn-primary">
|
||||
{{ t('common.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { CheckResult } from '@/api/admin/channelMonitor'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
|
||||
defineProps<{
|
||||
show: boolean
|
||||
results: CheckResult[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { statusLabel, statusBadgeClass, formatLatency } = useChannelMonitorFormat()
|
||||
</script>
|
||||
@@ -38,7 +38,7 @@
|
||||
'sidebar-link-collapsed': sidebarCollapsed
|
||||
}"
|
||||
:title="sidebarCollapsed ? item.label : undefined"
|
||||
@click="sidebarCollapsed ? undefined : toggleGroup(item)"
|
||||
@click="handleGroupClick(item)"
|
||||
>
|
||||
<component :is="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<span
|
||||
@@ -181,7 +181,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAdminSettingsStore, useAppStore, useAuthStore, useOnboardingStore } from '@/stores'
|
||||
import VersionBadge from '@/components/common/VersionBadge.vue'
|
||||
@@ -194,11 +194,17 @@ interface NavItem {
|
||||
iconSvg?: string
|
||||
hideInSimpleMode?: boolean
|
||||
children?: NavItem[]
|
||||
/**
|
||||
* When true, the parent item only toggles the expand/collapse state and
|
||||
* does NOT navigate to its `path`. The `path` is purely a stable key.
|
||||
*/
|
||||
expandOnly?: boolean
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const appStore = useAppStore()
|
||||
const authStore = useAuthStore()
|
||||
const onboardingStore = useOnboardingStore()
|
||||
@@ -549,6 +555,41 @@ const ChevronDoubleRightIcon = {
|
||||
)
|
||||
}
|
||||
|
||||
const SignalIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'M9.348 14.651a3.75 3.75 0 010-5.303m5.304 0a3.75 3.75 0 010 5.303m-7.425 2.122a6.75 6.75 0 010-9.546m9.546 0a6.75 6.75 0 010 9.546M5.106 18.894c-3.808-3.807-3.808-9.98 0-13.788m13.788 0c3.808 3.807 3.808 9.98 0 13.788M12 12h.008v.008H12V12zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const PriceTagIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
'svg',
|
||||
{ fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor', 'stroke-width': '1.5' },
|
||||
[
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z'
|
||||
}),
|
||||
h('path', {
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
d: 'M6 6h.008v.008H6V6z'
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
const ChevronDownIcon = {
|
||||
render: () =>
|
||||
h(
|
||||
@@ -570,6 +611,7 @@ const userNavItems = computed((): NavItem[] => {
|
||||
{ path: '/dashboard', label: t('nav.dashboard'), icon: DashboardIcon },
|
||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/monitor', label: t('nav.channelStatus'), icon: SignalIcon },
|
||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
@@ -608,6 +650,7 @@ const personalNavItems = computed((): NavItem[] => {
|
||||
const items: NavItem[] = [
|
||||
{ path: '/keys', label: t('nav.apiKeys'), icon: KeyIcon },
|
||||
{ path: '/usage', label: t('nav.usage'), icon: ChartIcon, hideInSimpleMode: true },
|
||||
{ path: '/monitor', label: t('nav.channelStatus'), icon: SignalIcon },
|
||||
{ path: '/subscriptions', label: t('nav.mySubscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
...(appStore.cachedPublicSettings?.payment_enabled
|
||||
? [
|
||||
@@ -664,7 +707,17 @@ const adminNavItems = computed((): NavItem[] => {
|
||||
: []),
|
||||
{ path: '/admin/users', label: t('nav.users'), icon: UsersIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/groups', label: t('nav.groups'), icon: FolderIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/channels', label: t('nav.channels', '渠道管理'), icon: ChannelIcon, hideInSimpleMode: true },
|
||||
{
|
||||
path: '/admin/channels',
|
||||
label: t('nav.channelManagement'),
|
||||
icon: ChannelIcon,
|
||||
hideInSimpleMode: true,
|
||||
expandOnly: true,
|
||||
children: [
|
||||
{ path: '/admin/channels/pricing', label: t('nav.channelPricing'), icon: PriceTagIcon },
|
||||
{ path: '/admin/channels/monitor', label: t('nav.channelMonitor'), icon: SignalIcon },
|
||||
],
|
||||
},
|
||||
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },
|
||||
{ path: '/admin/accounts', label: t('nav.accounts'), icon: GlobeIcon },
|
||||
{ path: '/admin/announcements', label: t('nav.announcements'), icon: BellIcon },
|
||||
@@ -678,6 +731,7 @@ const adminNavItems = computed((): NavItem[] => {
|
||||
label: t('nav.orderManagement'),
|
||||
icon: OrderIcon,
|
||||
hideInSimpleMode: true,
|
||||
expandOnly: true,
|
||||
children: [
|
||||
{ path: '/admin/orders/dashboard', label: t('nav.paymentDashboard'), icon: ChartIcon },
|
||||
{ path: '/admin/orders', label: t('nav.orderManagement'), icon: OrderIcon },
|
||||
@@ -764,6 +818,28 @@ function toggleGroup(item: NavItem) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for collapsible parent items.
|
||||
* - When sidebar is collapsed: do nothing (children are not visible).
|
||||
* - When `expandOnly` is true: only toggle expand state.
|
||||
* - Otherwise (default, e.g. /admin/orders): navigate to the parent path
|
||||
* (router-link semantics) and ensure the group is expanded.
|
||||
*/
|
||||
function handleGroupClick(item: NavItem) {
|
||||
if (sidebarCollapsed.value) return
|
||||
if (item.expandOnly) {
|
||||
toggleGroup(item)
|
||||
return
|
||||
}
|
||||
// Push to path and ensure expanded
|
||||
if (route.path !== item.path) {
|
||||
router.push(item.path)
|
||||
}
|
||||
if (!expandedGroups.value.has(item.path)) {
|
||||
expandedGroups.value.add(item.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
if (
|
||||
|
||||
114
frontend/src/components/user/MonitorDetailDialog.vue
Normal file
114
frontend/src/components/user/MonitorDetailDialog.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:show="show"
|
||||
:title="title"
|
||||
width="wide"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<div v-if="loading" class="py-8 text-center text-sm text-gray-500">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
<div v-else-if="!detail" class="py-8 text-center text-sm text-gray-500">
|
||||
{{ t('channelStatus.detailLoadError') }}
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="border-b border-gray-200 dark:border-dark-700">
|
||||
<tr class="text-xs uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.model') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.latestStatus') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.latestLatency') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.availability7d') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.availability15d') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.availability30d') }}</th>
|
||||
<th class="py-2 pr-3">{{ t('channelStatus.detailColumns.avgLatency7d') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="m in detail.models"
|
||||
:key="m.model"
|
||||
class="border-b border-gray-100 dark:border-dark-800"
|
||||
>
|
||||
<td class="py-2 pr-3 font-medium text-gray-900 dark:text-gray-100">{{ m.model }}</td>
|
||||
<td class="py-2 pr-3">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px]"
|
||||
:class="statusBadgeClass(m.latest_status)"
|
||||
>
|
||||
{{ statusLabel(m.latest_status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-2 pr-3 text-gray-700 dark:text-gray-300">{{ formatLatency(m.latest_latency_ms) }}</td>
|
||||
<td class="py-2 pr-3 text-gray-700 dark:text-gray-300">{{ formatPercent(m.availability_7d) }}</td>
|
||||
<td class="py-2 pr-3 text-gray-700 dark:text-gray-300">{{ formatPercent(m.availability_15d) }}</td>
|
||||
<td class="py-2 pr-3 text-gray-700 dark:text-gray-300">{{ formatPercent(m.availability_30d) }}</td>
|
||||
<td class="py-2 pr-3 text-gray-700 dark:text-gray-300">{{ formatLatency(m.avg_latency_7d_ms) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end">
|
||||
<button @click="$emit('close')" class="btn btn-secondary">
|
||||
{{ t('channelStatus.closeDetail') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import {
|
||||
status as fetchChannelMonitorDetail,
|
||||
type UserMonitorDetail,
|
||||
} from '@/api/channelMonitor'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
monitorId: number | null
|
||||
title: string
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: 'close'): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const { statusLabel, statusBadgeClass, formatLatency, formatPercent } = useChannelMonitorFormat()
|
||||
|
||||
const detail = ref<UserMonitorDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
async function load(id: number) {
|
||||
detail.value = null
|
||||
loading.value = true
|
||||
try {
|
||||
detail.value = await fetchChannelMonitorDetail(id)
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('channelStatus.detailLoadError')))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.show, props.monitorId] as const,
|
||||
([show, id]) => {
|
||||
if (!show) {
|
||||
detail.value = null
|
||||
return
|
||||
}
|
||||
if (id != null) void load(id)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
71
frontend/src/components/user/MonitorPrimaryModelCell.vue
Normal file
71
frontend/src/components/user/MonitorPrimaryModelCell.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">{{ row.primary_model }}</span>
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium"
|
||||
:class="statusBadgeClass(row.primary_status)"
|
||||
>
|
||||
{{ statusLabel(row.primary_status) }}
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs font-semibold text-gray-100">
|
||||
{{ row.primary_model }}
|
||||
<span
|
||||
class="ml-1 inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium"
|
||||
:class="statusBadgeClass(row.primary_status)"
|
||||
>
|
||||
{{ statusLabel(row.primary_status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="(row.extra_models?.length ?? 0) === 0" class="text-[11px] text-gray-300">
|
||||
{{ t('monitorCommon.extraModelsEmpty') }}
|
||||
</div>
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-400">
|
||||
{{ t('monitorCommon.extraModelsHeader') }}
|
||||
</div>
|
||||
<table class="w-full text-left text-[11px]">
|
||||
<thead>
|
||||
<tr class="text-gray-400">
|
||||
<th class="py-0.5 pr-2 font-medium">{{ t('channelStatus.detailColumns.model') }}</th>
|
||||
<th class="py-0.5 pr-2 font-medium">{{ t('channelStatus.detailColumns.latestStatus') }}</th>
|
||||
<th class="py-0.5 font-medium">{{ t('channelStatus.detailColumns.latestLatency') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in row.extra_models" :key="m.model">
|
||||
<td class="py-0.5 pr-2 text-gray-100">{{ m.model }}</td>
|
||||
<td class="py-0.5 pr-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px]"
|
||||
:class="statusBadgeClass(m.status)"
|
||||
>
|
||||
{{ statusLabel(m.status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-0.5 text-gray-100">{{ formatLatency(m.latency_ms) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { UserMonitorView } from '@/api/channelMonitor'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
|
||||
defineProps<{
|
||||
row: UserMonitorView
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const { statusLabel, statusBadgeClass, formatLatency } = useChannelMonitorFormat()
|
||||
</script>
|
||||
97
frontend/src/composables/useChannelMonitorFormat.ts
Normal file
97
frontend/src/composables/useChannelMonitorFormat.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Shared formatting helpers for channel monitor views (admin + user).
|
||||
*
|
||||
* Centralises:
|
||||
* - status / provider label + badge class lookups
|
||||
* - latency / availability / percent number formatting
|
||||
*
|
||||
* i18n keys live under `monitorCommon.*` so admin and user views share the
|
||||
* same translation source.
|
||||
*/
|
||||
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { MonitorStatus, Provider } from '@/api/admin/channelMonitor'
|
||||
import {
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
STATUS_OPERATIONAL,
|
||||
STATUS_DEGRADED,
|
||||
STATUS_FAILED,
|
||||
STATUS_ERROR,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
const NEUTRAL_BADGE = 'bg-gray-100 text-gray-800 dark:bg-dark-700 dark:text-gray-300'
|
||||
|
||||
export interface AvailabilityRow {
|
||||
primary_status: MonitorStatus | ''
|
||||
availability_7d: number | null | undefined
|
||||
}
|
||||
|
||||
export function useChannelMonitorFormat() {
|
||||
const { t } = useI18n()
|
||||
|
||||
function statusLabel(s: MonitorStatus | ''): string {
|
||||
if (!s) return t('monitorCommon.status.unknown')
|
||||
return t(`monitorCommon.status.${s}`)
|
||||
}
|
||||
|
||||
function statusBadgeClass(s: MonitorStatus | ''): string {
|
||||
switch (s) {
|
||||
case STATUS_OPERATIONAL:
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||
case STATUS_DEGRADED:
|
||||
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300'
|
||||
case STATUS_FAILED:
|
||||
return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300'
|
||||
case STATUS_ERROR:
|
||||
default:
|
||||
return NEUTRAL_BADGE
|
||||
}
|
||||
}
|
||||
|
||||
function providerLabel(p: Provider | string): string {
|
||||
if (p === PROVIDER_OPENAI || p === PROVIDER_ANTHROPIC || p === PROVIDER_GEMINI) {
|
||||
return t(`monitorCommon.providers.${p}`)
|
||||
}
|
||||
return p || '-'
|
||||
}
|
||||
|
||||
function providerBadgeClass(p: Provider | string): string {
|
||||
switch (p) {
|
||||
case PROVIDER_OPENAI:
|
||||
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
|
||||
case PROVIDER_ANTHROPIC:
|
||||
return 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300'
|
||||
case PROVIDER_GEMINI:
|
||||
return 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'
|
||||
default:
|
||||
return NEUTRAL_BADGE
|
||||
}
|
||||
}
|
||||
|
||||
function formatLatency(ms: number | null | undefined): string {
|
||||
if (ms == null) return t('monitorCommon.latencyEmpty')
|
||||
return String(Math.round(ms))
|
||||
}
|
||||
|
||||
function formatPercent(v: number | null | undefined): string {
|
||||
if (v == null || Number.isNaN(v)) return '-'
|
||||
return `${v.toFixed(2)}%`
|
||||
}
|
||||
|
||||
function formatAvailability(row: AvailabilityRow): string {
|
||||
if (!row.primary_status) return '-'
|
||||
return formatPercent(row.availability_7d)
|
||||
}
|
||||
|
||||
return {
|
||||
statusLabel,
|
||||
statusBadgeClass,
|
||||
providerLabel,
|
||||
providerBadgeClass,
|
||||
formatLatency,
|
||||
formatPercent,
|
||||
formatAvailability,
|
||||
}
|
||||
}
|
||||
35
frontend/src/constants/channelMonitor.ts
Normal file
35
frontend/src/constants/channelMonitor.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Channel monitor shared constants.
|
||||
*
|
||||
* Single source of truth for provider/status string values used by both the
|
||||
* admin (`views/admin/ChannelMonitorView.vue`) and user-facing
|
||||
* (`views/user/ChannelStatusView.vue`) screens, plus the shared composable
|
||||
* `useChannelMonitorFormat`.
|
||||
*/
|
||||
|
||||
import type { Provider, MonitorStatus } from '@/api/admin/channelMonitor'
|
||||
|
||||
export const PROVIDER_OPENAI: Provider = 'openai'
|
||||
export const PROVIDER_ANTHROPIC: Provider = 'anthropic'
|
||||
export const PROVIDER_GEMINI: Provider = 'gemini'
|
||||
|
||||
export const PROVIDERS: readonly Provider[] = [
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
]
|
||||
|
||||
export const STATUS_OPERATIONAL: MonitorStatus = 'operational'
|
||||
export const STATUS_DEGRADED: MonitorStatus = 'degraded'
|
||||
export const STATUS_FAILED: MonitorStatus = 'failed'
|
||||
export const STATUS_ERROR: MonitorStatus = 'error'
|
||||
|
||||
export const MONITOR_STATUSES: readonly MonitorStatus[] = [
|
||||
STATUS_OPERATIONAL,
|
||||
STATUS_DEGRADED,
|
||||
STATUS_FAILED,
|
||||
STATUS_ERROR,
|
||||
]
|
||||
|
||||
/** Default polling interval (seconds) for new monitors. */
|
||||
export const DEFAULT_INTERVAL_SECONDS = 60
|
||||
@@ -245,6 +245,7 @@ export default {
|
||||
// Common
|
||||
common: {
|
||||
loading: 'Loading...',
|
||||
submitting: 'Submitting...',
|
||||
justNow: 'just now',
|
||||
save: 'Save',
|
||||
saved: 'Saved successfully',
|
||||
@@ -363,7 +364,11 @@ export default {
|
||||
orderManagement: 'Orders',
|
||||
paymentDashboard: 'Payment Dashboard',
|
||||
paymentConfig: 'Payment Config',
|
||||
paymentPlans: 'Plans'
|
||||
paymentPlans: 'Plans',
|
||||
channelManagement: 'Channels',
|
||||
channelPricing: 'Channel Pricing',
|
||||
channelMonitor: 'Channel Monitor',
|
||||
channelStatus: 'Channel Status',
|
||||
},
|
||||
|
||||
// Auth
|
||||
@@ -846,6 +851,58 @@ export default {
|
||||
userAgent: 'User-Agent'
|
||||
},
|
||||
|
||||
// Shared keys for channel monitor (admin + user views)
|
||||
monitorCommon: {
|
||||
status: {
|
||||
operational: 'Operational',
|
||||
degraded: 'Degraded',
|
||||
failed: 'Failed',
|
||||
error: 'Error',
|
||||
unknown: '-'
|
||||
},
|
||||
providers: {
|
||||
openai: 'OpenAI',
|
||||
anthropic: 'Anthropic',
|
||||
gemini: 'Gemini'
|
||||
},
|
||||
extraModelsHeader: 'Extra Models',
|
||||
extraModelsEmpty: 'No extra models',
|
||||
latencyEmpty: '-'
|
||||
},
|
||||
|
||||
// Channel Status (user-facing read-only view)
|
||||
channelStatus: {
|
||||
title: 'Channel Status',
|
||||
description: 'Inspect channel availability, latency and recent status',
|
||||
searchPlaceholder: 'Search channels...',
|
||||
allProviders: 'All Providers',
|
||||
loadError: 'Failed to load channel status',
|
||||
detailLoadError: 'Failed to load channel detail',
|
||||
detailTitle: 'Channel Detail',
|
||||
closeDetail: 'Close',
|
||||
columns: {
|
||||
name: 'Name',
|
||||
provider: 'Provider',
|
||||
groupName: 'Group',
|
||||
primaryModel: 'Primary Model',
|
||||
availability7d: '7d Availability',
|
||||
latency: 'Latency (ms)'
|
||||
},
|
||||
detailColumns: {
|
||||
model: 'Model',
|
||||
latestStatus: 'Latest Status',
|
||||
latestLatency: 'Latest Latency (ms)',
|
||||
availability7d: '7d Availability',
|
||||
availability15d: '15d Availability',
|
||||
availability30d: '30d Availability',
|
||||
avgLatency7d: '7d Avg Latency (ms)'
|
||||
},
|
||||
empty: {
|
||||
title: 'No channels available',
|
||||
description: 'No monitored channels have been configured yet.'
|
||||
}
|
||||
},
|
||||
|
||||
// Redeem
|
||||
redeem: {
|
||||
title: 'Redeem Code',
|
||||
@@ -2014,6 +2071,69 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// Channel Monitor
|
||||
channelMonitor: {
|
||||
title: 'Channel Monitor',
|
||||
description: 'Monitor channel availability, latency and status',
|
||||
searchPlaceholder: 'Search monitor name...',
|
||||
allProviders: 'All Providers',
|
||||
allStatus: 'All Status',
|
||||
enabledFilter: 'Enabled',
|
||||
onlyEnabled: 'Enabled only',
|
||||
onlyDisabled: 'Disabled only',
|
||||
createButton: 'Create Monitor',
|
||||
createTitle: 'Create Channel Monitor',
|
||||
editTitle: 'Edit Channel Monitor',
|
||||
runNow: 'Run Now',
|
||||
runSuccess: 'Check completed',
|
||||
runFailed: 'Check failed',
|
||||
apiKeyDecryptFailed: 'API Key decryption failed. Please re-edit this monitor with a fresh key.',
|
||||
createSuccess: 'Monitor created',
|
||||
updateSuccess: 'Monitor updated',
|
||||
deleteSuccess: 'Monitor deleted',
|
||||
loadError: 'Failed to load monitors',
|
||||
deleteConfirm: 'Are you sure you want to delete monitor "{name}"? This action cannot be undone.',
|
||||
nameRequired: 'Please enter a monitor name',
|
||||
primaryModelRequired: 'Please enter a primary model',
|
||||
columns: {
|
||||
name: 'Name',
|
||||
provider: 'Provider',
|
||||
primaryModel: 'Primary Model',
|
||||
availability7d: '7d Availability',
|
||||
latency: 'Latency (ms)',
|
||||
enabled: 'Enabled',
|
||||
actions: 'Actions'
|
||||
},
|
||||
form: {
|
||||
name: 'Name',
|
||||
namePlaceholder: 'Enter monitor name',
|
||||
provider: 'Provider',
|
||||
endpoint: 'Endpoint',
|
||||
endpointPlaceholder: 'https://api.example.com',
|
||||
useCurrentDomain: 'Use current service',
|
||||
apiKey: 'API Key',
|
||||
apiKeyPlaceholder: 'Enter API Key',
|
||||
apiKeyEditPlaceholder: 'Leave blank to keep current key',
|
||||
useMyKey: 'Use my key',
|
||||
selectKeyTitle: 'Select my API Key',
|
||||
selectKeyHint: 'Only your active, non-expired keys are listed.',
|
||||
noActiveKey: 'No active API keys available',
|
||||
primaryModel: 'Primary Model',
|
||||
primaryModelPlaceholder: 'gpt-4o-mini',
|
||||
extraModels: 'Extra Models',
|
||||
extraModelsPlaceholder: 'Press Enter to add extra model',
|
||||
groupName: 'Group Name',
|
||||
groupNamePlaceholder: 'Optional, used to group rows in user view',
|
||||
intervalSeconds: 'Interval (seconds)',
|
||||
intervalSecondsHint: 'Range: 15 - 3600 seconds',
|
||||
enabled: 'Enable monitor',
|
||||
kindRequired: 'Please select a provider'
|
||||
},
|
||||
runResultTitle: 'Check Result',
|
||||
noMonitorsYet: 'No monitors yet',
|
||||
createFirstMonitor: 'Create your first monitor to track channel availability'
|
||||
},
|
||||
|
||||
// Subscriptions
|
||||
subscriptions: {
|
||||
title: 'Subscription Management',
|
||||
|
||||
@@ -245,6 +245,7 @@ export default {
|
||||
// Common
|
||||
common: {
|
||||
loading: '加载中...',
|
||||
submitting: '提交中...',
|
||||
justNow: '刚刚',
|
||||
save: '保存',
|
||||
saved: '保存成功',
|
||||
@@ -363,7 +364,11 @@ export default {
|
||||
orderManagement: '订单管理',
|
||||
paymentDashboard: '支付概览',
|
||||
paymentConfig: '支付配置',
|
||||
paymentPlans: '订阅套餐'
|
||||
paymentPlans: '订阅套餐',
|
||||
channelManagement: '渠道管理',
|
||||
channelPricing: '渠道定价',
|
||||
channelMonitor: '渠道监控',
|
||||
channelStatus: '渠道状态',
|
||||
},
|
||||
|
||||
// Auth
|
||||
@@ -850,6 +855,58 @@ export default {
|
||||
userAgent: 'User-Agent'
|
||||
},
|
||||
|
||||
// Shared keys for channel monitor (admin + user views)
|
||||
monitorCommon: {
|
||||
status: {
|
||||
operational: '正常',
|
||||
degraded: '降级',
|
||||
failed: '失败',
|
||||
error: '错误',
|
||||
unknown: '-'
|
||||
},
|
||||
providers: {
|
||||
openai: 'OpenAI',
|
||||
anthropic: 'Anthropic',
|
||||
gemini: 'Gemini'
|
||||
},
|
||||
extraModelsHeader: '附加模型',
|
||||
extraModelsEmpty: '无附加模型',
|
||||
latencyEmpty: '-'
|
||||
},
|
||||
|
||||
// Channel Status (user-facing read-only view)
|
||||
channelStatus: {
|
||||
title: '渠道状态',
|
||||
description: '查看渠道可用性、延迟和近期状态',
|
||||
searchPlaceholder: '搜索渠道...',
|
||||
allProviders: '全部供应商',
|
||||
loadError: '加载渠道状态失败',
|
||||
detailLoadError: '加载渠道详情失败',
|
||||
detailTitle: '渠道详情',
|
||||
closeDetail: '关闭',
|
||||
columns: {
|
||||
name: '名称',
|
||||
provider: '供应商',
|
||||
groupName: '分组',
|
||||
primaryModel: '主模型',
|
||||
availability7d: '7 天可用率',
|
||||
latency: '延迟 (ms)'
|
||||
},
|
||||
detailColumns: {
|
||||
model: '模型',
|
||||
latestStatus: '最新状态',
|
||||
latestLatency: '最新延迟 (ms)',
|
||||
availability7d: '7 天可用率',
|
||||
availability15d: '15 天可用率',
|
||||
availability30d: '30 天可用率',
|
||||
avgLatency7d: '7 天平均延迟 (ms)'
|
||||
},
|
||||
empty: {
|
||||
title: '暂无可显示的渠道',
|
||||
description: '管理员尚未配置可监控的渠道。'
|
||||
}
|
||||
},
|
||||
|
||||
// Redeem
|
||||
redeem: {
|
||||
title: '兑换码',
|
||||
@@ -2093,6 +2150,69 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// Channel Monitor
|
||||
channelMonitor: {
|
||||
title: '渠道监控',
|
||||
description: '监测各渠道的可用性、延迟和状态',
|
||||
searchPlaceholder: '搜索监控名称...',
|
||||
allProviders: '全部供应商',
|
||||
allStatus: '全部状态',
|
||||
enabledFilter: '启用状态',
|
||||
onlyEnabled: '仅启用',
|
||||
onlyDisabled: '仅禁用',
|
||||
createButton: '新增监控',
|
||||
createTitle: '新增渠道监控',
|
||||
editTitle: '编辑渠道监控',
|
||||
runNow: '立即检测',
|
||||
runSuccess: '检测完成',
|
||||
runFailed: '检测失败',
|
||||
apiKeyDecryptFailed: 'API Key 解密失败,请重新编辑该监控并填入新的 Key',
|
||||
createSuccess: '监控创建成功',
|
||||
updateSuccess: '监控更新成功',
|
||||
deleteSuccess: '监控删除成功',
|
||||
loadError: '加载监控列表失败',
|
||||
deleteConfirm: '确定要删除监控「{name}」吗?此操作不可撤销。',
|
||||
nameRequired: '请输入监控名称',
|
||||
primaryModelRequired: '请输入主模型',
|
||||
columns: {
|
||||
name: '名称',
|
||||
provider: '供应商',
|
||||
primaryModel: '主模型',
|
||||
availability7d: '7 天可用率',
|
||||
latency: '延迟 (ms)',
|
||||
enabled: '启用',
|
||||
actions: '操作'
|
||||
},
|
||||
form: {
|
||||
name: '名称',
|
||||
namePlaceholder: '输入监控名称',
|
||||
provider: '供应商',
|
||||
endpoint: '上游地址',
|
||||
endpointPlaceholder: 'https://api.example.com',
|
||||
useCurrentDomain: '使用当前服务',
|
||||
apiKey: 'API Key',
|
||||
apiKeyPlaceholder: '请输入 API Key',
|
||||
apiKeyEditPlaceholder: '留空表示不修改',
|
||||
useMyKey: '使用我的 Key',
|
||||
selectKeyTitle: '选择我的 API Key',
|
||||
selectKeyHint: '仅显示当前账号下处于「启用」状态且未过期的 Key。',
|
||||
noActiveKey: '没有可用的启用状态 Key',
|
||||
primaryModel: '主模型',
|
||||
primaryModelPlaceholder: 'gpt-4o-mini',
|
||||
extraModels: '附加模型',
|
||||
extraModelsPlaceholder: '回车添加附加模型',
|
||||
groupName: '分组名称',
|
||||
groupNamePlaceholder: '可选,用于在用户视图中聚合显示',
|
||||
intervalSeconds: '检测间隔 (秒)',
|
||||
intervalSecondsHint: '范围:15 - 3600 秒',
|
||||
enabled: '启用监控',
|
||||
kindRequired: '请选择供应商'
|
||||
},
|
||||
runResultTitle: '检测结果',
|
||||
noMonitorsYet: '暂无监控',
|
||||
createFirstMonitor: '创建第一个监控来跟踪渠道可用性'
|
||||
},
|
||||
|
||||
// Subscriptions Management
|
||||
subscriptions: {
|
||||
title: '订阅管理',
|
||||
|
||||
@@ -360,6 +360,10 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: '/admin/channels',
|
||||
redirect: '/admin/channels/pricing'
|
||||
},
|
||||
{
|
||||
path: '/admin/channels/pricing',
|
||||
name: 'AdminChannels',
|
||||
component: () => import('@/views/admin/ChannelsView.vue'),
|
||||
meta: {
|
||||
@@ -370,6 +374,29 @@ const routes: RouteRecordRaw[] = [
|
||||
descriptionKey: 'admin.channels.description'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/admin/channels/monitor',
|
||||
name: 'AdminChannelMonitor',
|
||||
component: () => import('@/views/admin/ChannelMonitorView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
requiresAdmin: true,
|
||||
title: 'Channel Monitor',
|
||||
titleKey: 'admin.channelMonitor.title',
|
||||
descriptionKey: 'admin.channelMonitor.description'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/monitor',
|
||||
name: 'ChannelStatus',
|
||||
component: () => import('@/views/user/ChannelStatusView.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
requiresAdmin: false,
|
||||
title: 'Channel Status',
|
||||
titleKey: 'nav.channelStatus'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/admin/subscriptions',
|
||||
name: 'AdminSubscriptions',
|
||||
|
||||
295
frontend/src/views/admin/ChannelMonitorView.vue
Normal file
295
frontend/src/views/admin/ChannelMonitorView.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<AppLayout>
|
||||
<TablePageLayout>
|
||||
<template #filters>
|
||||
<MonitorFiltersBar
|
||||
v-model:search="searchQuery"
|
||||
v-model:provider="providerFilter"
|
||||
v-model:enabled="enabledFilter"
|
||||
:loading="loading"
|
||||
@reload="reload"
|
||||
@create="openCreateDialog"
|
||||
@search-input="handleSearch"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #table>
|
||||
<DataTable :columns="columns" :data="monitors" :loading="loading">
|
||||
<template #cell-name="{ row, value }">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ value }}</span>
|
||||
<HelpTooltip v-if="row.api_key_decrypt_failed" :content="t('admin.channelMonitor.apiKeyDecryptFailed')">
|
||||
<Icon name="exclamationTriangle" size="sm" class="text-red-500" />
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #cell-provider="{ row }">
|
||||
<span class="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium" :class="providerBadgeClass(row.provider)">
|
||||
{{ providerLabel(row.provider) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-primary_model="{ row }">
|
||||
<MonitorPrimaryModelCell :row="row" />
|
||||
</template>
|
||||
|
||||
<template #cell-availability_7d="{ row }">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">{{ formatAvailability(row) }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-latency="{ row }">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">{{ formatLatency(row.primary_latency_ms) }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-enabled="{ row }">
|
||||
<Toggle :modelValue="row.enabled" @update:modelValue="toggleEnabled(row)" />
|
||||
</template>
|
||||
|
||||
<template #cell-actions="{ row }">
|
||||
<MonitorActionsCell
|
||||
:row="row"
|
||||
:running="runningId === row.id"
|
||||
@run="handleRunNow"
|
||||
@edit="openEditDialog"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<EmptyState
|
||||
:title="t('admin.channelMonitor.noMonitorsYet')"
|
||||
:description="t('admin.channelMonitor.createFirstMonitor')"
|
||||
:action-text="t('admin.channelMonitor.createButton')"
|
||||
@action="openCreateDialog"
|
||||
/>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
|
||||
<template #pagination>
|
||||
<Pagination
|
||||
v-if="pagination.total > 0"
|
||||
:page="pagination.page"
|
||||
:total="pagination.total"
|
||||
:page-size="pagination.page_size"
|
||||
@update:page="onPageChange"
|
||||
@update:pageSize="onPageSizeChange"
|
||||
/>
|
||||
</template>
|
||||
</TablePageLayout>
|
||||
|
||||
<MonitorFormDialog
|
||||
:show="showDialog"
|
||||
:monitor="editing"
|
||||
@close="closeDialog"
|
||||
@saved="reload"
|
||||
/>
|
||||
|
||||
<MonitorRunResultDialog
|
||||
:show="showRunResult"
|
||||
:results="runResults"
|
||||
@close="showRunResult = false"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:show="showDeleteDialog"
|
||||
:title="t('common.delete')"
|
||||
:message="deleteConfirmMessage"
|
||||
:confirm-text="t('common.delete')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:danger="true"
|
||||
@confirm="confirmDelete"
|
||||
@cancel="showDeleteDialog = false"
|
||||
/>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type {
|
||||
ChannelMonitor,
|
||||
CheckResult,
|
||||
ListParams,
|
||||
Provider,
|
||||
} from '@/api/admin/channelMonitor'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
||||
import DataTable from '@/components/common/DataTable.vue'
|
||||
import Pagination from '@/components/common/Pagination.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
import MonitorFiltersBar from '@/components/admin/monitor/MonitorFiltersBar.vue'
|
||||
import MonitorFormDialog from '@/components/admin/monitor/MonitorFormDialog.vue'
|
||||
import MonitorRunResultDialog from '@/components/admin/monitor/MonitorRunResultDialog.vue'
|
||||
import MonitorPrimaryModelCell from '@/components/admin/monitor/MonitorPrimaryModelCell.vue'
|
||||
import MonitorActionsCell from '@/components/admin/monitor/MonitorActionsCell.vue'
|
||||
import { getPersistedPageSize } from '@/composables/usePersistedPageSize'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const {
|
||||
providerLabel,
|
||||
providerBadgeClass,
|
||||
formatLatency,
|
||||
formatAvailability,
|
||||
} = useChannelMonitorFormat()
|
||||
|
||||
const monitors = ref<ChannelMonitor[]>([])
|
||||
const loading = ref(false)
|
||||
const runningId = ref<number | null>(null)
|
||||
const searchQuery = ref('')
|
||||
const providerFilter = ref<Provider | ''>('')
|
||||
const enabledFilter = ref<'' | 'true' | 'false'>('')
|
||||
const pagination = reactive({ page: 1, page_size: getPersistedPageSize(), total: 0 })
|
||||
|
||||
const showDialog = ref(false)
|
||||
const editing = ref<ChannelMonitor | null>(null)
|
||||
const showDeleteDialog = ref(false)
|
||||
const deleting = ref<ChannelMonitor | null>(null)
|
||||
const showRunResult = ref(false)
|
||||
const runResults = ref<CheckResult[]>([])
|
||||
|
||||
let abortController: AbortController | null = null
|
||||
let searchTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const columns = computed<Column[]>(() => [
|
||||
{ key: 'name', label: t('admin.channelMonitor.columns.name'), sortable: false },
|
||||
{ key: 'provider', label: t('admin.channelMonitor.columns.provider'), sortable: false },
|
||||
{ key: 'primary_model', label: t('admin.channelMonitor.columns.primaryModel'), sortable: false },
|
||||
{ key: 'availability_7d', label: t('admin.channelMonitor.columns.availability7d'), sortable: false },
|
||||
{ key: 'latency', label: t('admin.channelMonitor.columns.latency'), sortable: false },
|
||||
{ key: 'enabled', label: t('admin.channelMonitor.columns.enabled'), sortable: false },
|
||||
{ key: 'actions', label: t('admin.channelMonitor.columns.actions'), sortable: false },
|
||||
])
|
||||
|
||||
const deleteConfirmMessage = computed(() => {
|
||||
const name = deleting.value?.name || ''
|
||||
return t('admin.channelMonitor.deleteConfirm', { name })
|
||||
})
|
||||
|
||||
async function reload() {
|
||||
if (abortController) abortController.abort()
|
||||
const ctrl = new AbortController()
|
||||
abortController = ctrl
|
||||
loading.value = true
|
||||
try {
|
||||
const params: ListParams = {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
}
|
||||
if (providerFilter.value) params.provider = providerFilter.value
|
||||
if (enabledFilter.value === 'true') params.enabled = true
|
||||
if (enabledFilter.value === 'false') params.enabled = false
|
||||
if (searchQuery.value.trim()) params.search = searchQuery.value.trim()
|
||||
|
||||
const res = await adminAPI.channelMonitor.list(params, { signal: ctrl.signal })
|
||||
if (ctrl.signal.aborted || abortController !== ctrl) return
|
||||
monitors.value = res.items || []
|
||||
pagination.total = res.total
|
||||
} catch (err: unknown) {
|
||||
const e = err as { name?: string; code?: string }
|
||||
if (e?.name === 'AbortError' || e?.code === 'ERR_CANCELED') return
|
||||
appStore.showError(extractApiErrorMessage(err, t('admin.channelMonitor.loadError')))
|
||||
} finally {
|
||||
if (abortController === ctrl) {
|
||||
loading.value = false
|
||||
abortController = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
if (searchTimeout) clearTimeout(searchTimeout)
|
||||
searchTimeout = setTimeout(() => {
|
||||
pagination.page = 1
|
||||
reload()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function onPageChange(page: number) {
|
||||
pagination.page = page
|
||||
reload()
|
||||
}
|
||||
|
||||
function onPageSizeChange(size: number) {
|
||||
pagination.page_size = size
|
||||
pagination.page = 1
|
||||
reload()
|
||||
}
|
||||
|
||||
function openCreateDialog() {
|
||||
editing.value = null
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
function openEditDialog(row: ChannelMonitor) {
|
||||
editing.value = row
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
showDialog.value = false
|
||||
editing.value = null
|
||||
}
|
||||
|
||||
async function toggleEnabled(row: ChannelMonitor) {
|
||||
const next = !row.enabled
|
||||
try {
|
||||
await adminAPI.channelMonitor.update(row.id, { enabled: next })
|
||||
row.enabled = next
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('common.error')))
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRunNow(row: ChannelMonitor) {
|
||||
if (runningId.value != null) return
|
||||
runningId.value = row.id
|
||||
try {
|
||||
const res = await adminAPI.channelMonitor.runNow(row.id)
|
||||
runResults.value = res.results || []
|
||||
showRunResult.value = true
|
||||
appStore.showSuccess(t('admin.channelMonitor.runSuccess'))
|
||||
// Refresh row to get latest status from backend
|
||||
void reload()
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('admin.channelMonitor.runFailed')))
|
||||
} finally {
|
||||
runningId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(row: ChannelMonitor) {
|
||||
deleting.value = row
|
||||
showDeleteDialog.value = true
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (!deleting.value) return
|
||||
try {
|
||||
await adminAPI.channelMonitor.del(deleting.value.id)
|
||||
appStore.showSuccess(t('admin.channelMonitor.deleteSuccess'))
|
||||
showDeleteDialog.value = false
|
||||
deleting.value = null
|
||||
reload()
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('common.error')))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(reload)
|
||||
onUnmounted(() => {
|
||||
if (searchTimeout) clearTimeout(searchTimeout)
|
||||
abortController?.abort()
|
||||
})
|
||||
</script>
|
||||
208
frontend/src/views/user/ChannelStatusView.vue
Normal file
208
frontend/src/views/user/ChannelStatusView.vue
Normal file
@@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<AppLayout>
|
||||
<TablePageLayout>
|
||||
<template #filters>
|
||||
<div class="flex flex-col justify-between gap-4 lg:flex-row lg:items-start">
|
||||
<div class="flex flex-1 flex-wrap items-center gap-3">
|
||||
<div class="relative w-full sm:w-64">
|
||||
<Icon
|
||||
name="search"
|
||||
size="md"
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
|
||||
/>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="t('channelStatus.searchPlaceholder')"
|
||||
class="input pl-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select
|
||||
v-model="providerFilter"
|
||||
:options="providerFilterOptions"
|
||||
:placeholder="t('channelStatus.allProviders')"
|
||||
class="w-44"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full flex-shrink-0 flex-wrap items-center justify-end gap-3 lg:w-auto">
|
||||
<button
|
||||
@click="reload"
|
||||
:disabled="loading"
|
||||
class="btn btn-secondary"
|
||||
:title="t('common.refresh')"
|
||||
>
|
||||
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #table>
|
||||
<DataTable :columns="columns" :data="filteredItems" :loading="loading">
|
||||
<template #cell-name="{ row }">
|
||||
<button
|
||||
@click="openDetail(row)"
|
||||
class="font-medium text-primary-600 transition-colors hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300"
|
||||
>
|
||||
{{ row.name }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template #cell-provider="{ row }">
|
||||
<span
|
||||
class="inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium"
|
||||
:class="providerBadgeClass(row.provider)"
|
||||
>
|
||||
{{ providerLabel(row.provider) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-group_name="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value || '-' }}</span>
|
||||
</template>
|
||||
|
||||
<template #cell-primary_model="{ row }">
|
||||
<MonitorPrimaryModelCell :row="row" />
|
||||
</template>
|
||||
|
||||
<template #cell-availability_7d="{ row }">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ formatAvailability(row) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #cell-latency="{ row }">
|
||||
<span class="text-sm text-gray-900 dark:text-gray-100">
|
||||
{{ formatLatency(row.primary_latency_ms) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #empty>
|
||||
<EmptyState
|
||||
:title="t('channelStatus.empty.title')"
|
||||
:description="t('channelStatus.empty.description')"
|
||||
/>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
</TablePageLayout>
|
||||
|
||||
<MonitorDetailDialog
|
||||
:show="showDetail"
|
||||
:monitor-id="detailTarget?.id ?? null"
|
||||
:title="detailTitle"
|
||||
@close="closeDetail"
|
||||
/>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { extractApiErrorMessage } from '@/utils/apiError'
|
||||
import {
|
||||
list as listChannelMonitorViews,
|
||||
type Provider,
|
||||
type UserMonitorView,
|
||||
} from '@/api/channelMonitor'
|
||||
import type { Column } from '@/components/common/types'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'
|
||||
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
|
||||
import DataTable from '@/components/common/DataTable.vue'
|
||||
import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import MonitorDetailDialog from '@/components/user/MonitorDetailDialog.vue'
|
||||
import MonitorPrimaryModelCell from '@/components/user/MonitorPrimaryModelCell.vue'
|
||||
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
|
||||
import {
|
||||
PROVIDER_OPENAI,
|
||||
PROVIDER_ANTHROPIC,
|
||||
PROVIDER_GEMINI,
|
||||
} from '@/constants/channelMonitor'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
const {
|
||||
providerLabel,
|
||||
providerBadgeClass,
|
||||
formatLatency,
|
||||
formatAvailability,
|
||||
} = useChannelMonitorFormat()
|
||||
|
||||
// ── State ──
|
||||
const items = ref<UserMonitorView[]>([])
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const providerFilter = ref<Provider | ''>('')
|
||||
|
||||
const showDetail = ref(false)
|
||||
const detailTarget = ref<UserMonitorView | null>(null)
|
||||
|
||||
// ── Options ──
|
||||
const providerFilterOptions = computed(() => [
|
||||
{ value: '', label: t('channelStatus.allProviders') },
|
||||
{ value: PROVIDER_OPENAI, label: providerLabel(PROVIDER_OPENAI) },
|
||||
{ value: PROVIDER_ANTHROPIC, label: providerLabel(PROVIDER_ANTHROPIC) },
|
||||
{ value: PROVIDER_GEMINI, label: providerLabel(PROVIDER_GEMINI) },
|
||||
])
|
||||
|
||||
// ── Columns ──
|
||||
const columns = computed<Column[]>(() => [
|
||||
{ key: 'name', label: t('channelStatus.columns.name'), sortable: false },
|
||||
{ key: 'provider', label: t('channelStatus.columns.provider'), sortable: false },
|
||||
{ key: 'group_name', label: t('channelStatus.columns.groupName'), sortable: false },
|
||||
{ key: 'primary_model', label: t('channelStatus.columns.primaryModel'), sortable: false },
|
||||
{ key: 'availability_7d', label: t('channelStatus.columns.availability7d'), sortable: false },
|
||||
{ key: 'latency', label: t('channelStatus.columns.latency'), sortable: false },
|
||||
])
|
||||
|
||||
// ── Filtered data ──
|
||||
const filteredItems = computed(() => {
|
||||
const q = searchQuery.value.trim().toLowerCase()
|
||||
return items.value.filter(it => {
|
||||
if (providerFilter.value && it.provider !== providerFilter.value) return false
|
||||
if (!q) return true
|
||||
return (
|
||||
it.name.toLowerCase().includes(q) ||
|
||||
(it.group_name || '').toLowerCase().includes(q) ||
|
||||
it.primary_model.toLowerCase().includes(q)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const detailTitle = computed(() => {
|
||||
return detailTarget.value?.name || t('channelStatus.detailTitle')
|
||||
})
|
||||
|
||||
// ── Loaders ──
|
||||
async function reload() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listChannelMonitorViews()
|
||||
items.value = res.items || []
|
||||
} catch (err: unknown) {
|
||||
appStore.showError(extractApiErrorMessage(err, t('channelStatus.loadError')))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(row: UserMonitorView) {
|
||||
detailTarget.value = row
|
||||
showDetail.value = true
|
||||
}
|
||||
|
||||
function closeDetail() {
|
||||
showDetail.value = false
|
||||
detailTarget.value = null
|
||||
}
|
||||
|
||||
// ── Lifecycle ──
|
||||
onMounted(() => {
|
||||
reload()
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user