mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 23:12:14 +08:00
Merge branch 'dev' into release
This commit is contained in:
@@ -17,7 +17,7 @@ func main() {
|
||||
email := flag.String("email", "", "Admin email to issue a JWT for (defaults to first active admin)")
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load()
|
||||
cfg, err := config.LoadForBootstrap()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load config: %v", err)
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ func runSetupServer() {
|
||||
}
|
||||
|
||||
func runMainServer() {
|
||||
cfg, err := config.Load()
|
||||
cfg, err := config.LoadForBootstrap()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||
@@ -65,6 +66,8 @@ type Client struct {
|
||||
Proxy *ProxyClient
|
||||
// RedeemCode is the client for interacting with the RedeemCode builders.
|
||||
RedeemCode *RedeemCodeClient
|
||||
// SecuritySecret is the client for interacting with the SecuritySecret builders.
|
||||
SecuritySecret *SecuritySecretClient
|
||||
// Setting is the client for interacting with the Setting builders.
|
||||
Setting *SettingClient
|
||||
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||
@@ -103,6 +106,7 @@ func (c *Client) init() {
|
||||
c.PromoCodeUsage = NewPromoCodeUsageClient(c.config)
|
||||
c.Proxy = NewProxyClient(c.config)
|
||||
c.RedeemCode = NewRedeemCodeClient(c.config)
|
||||
c.SecuritySecret = NewSecuritySecretClient(c.config)
|
||||
c.Setting = NewSettingClient(c.config)
|
||||
c.UsageCleanupTask = NewUsageCleanupTaskClient(c.config)
|
||||
c.UsageLog = NewUsageLogClient(c.config)
|
||||
@@ -214,6 +218,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
|
||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||
Proxy: NewProxyClient(cfg),
|
||||
RedeemCode: NewRedeemCodeClient(cfg),
|
||||
SecuritySecret: NewSecuritySecretClient(cfg),
|
||||
Setting: NewSettingClient(cfg),
|
||||
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||
UsageLog: NewUsageLogClient(cfg),
|
||||
@@ -252,6 +257,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
|
||||
PromoCodeUsage: NewPromoCodeUsageClient(cfg),
|
||||
Proxy: NewProxyClient(cfg),
|
||||
RedeemCode: NewRedeemCodeClient(cfg),
|
||||
SecuritySecret: NewSecuritySecretClient(cfg),
|
||||
Setting: NewSettingClient(cfg),
|
||||
UsageCleanupTask: NewUsageCleanupTaskClient(cfg),
|
||||
UsageLog: NewUsageLogClient(cfg),
|
||||
@@ -291,8 +297,8 @@ func (c *Client) Use(hooks ...Hook) {
|
||||
for _, n := range []interface{ Use(...Hook) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.ErrorPassthroughRule, c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy,
|
||||
c.RedeemCode, c.Setting, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.RedeemCode, c.SecuritySecret, c.Setting, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
} {
|
||||
n.Use(hooks...)
|
||||
@@ -305,8 +311,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
|
||||
for _, n := range []interface{ Intercept(...Interceptor) }{
|
||||
c.APIKey, c.Account, c.AccountGroup, c.Announcement, c.AnnouncementRead,
|
||||
c.ErrorPassthroughRule, c.Group, c.PromoCode, c.PromoCodeUsage, c.Proxy,
|
||||
c.RedeemCode, c.Setting, c.UsageCleanupTask, c.UsageLog, c.User,
|
||||
c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.RedeemCode, c.SecuritySecret, c.Setting, c.UsageCleanupTask, c.UsageLog,
|
||||
c.User, c.UserAllowedGroup, c.UserAttributeDefinition, c.UserAttributeValue,
|
||||
c.UserSubscription,
|
||||
} {
|
||||
n.Intercept(interceptors...)
|
||||
@@ -338,6 +344,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return c.Proxy.mutate(ctx, m)
|
||||
case *RedeemCodeMutation:
|
||||
return c.RedeemCode.mutate(ctx, m)
|
||||
case *SecuritySecretMutation:
|
||||
return c.SecuritySecret.mutate(ctx, m)
|
||||
case *SettingMutation:
|
||||
return c.Setting.mutate(ctx, m)
|
||||
case *UsageCleanupTaskMutation:
|
||||
@@ -2197,6 +2205,139 @@ func (c *RedeemCodeClient) mutate(ctx context.Context, m *RedeemCodeMutation) (V
|
||||
}
|
||||
}
|
||||
|
||||
// SecuritySecretClient is a client for the SecuritySecret schema.
|
||||
type SecuritySecretClient struct {
|
||||
config
|
||||
}
|
||||
|
||||
// NewSecuritySecretClient returns a client for the SecuritySecret from the given config.
|
||||
func NewSecuritySecretClient(c config) *SecuritySecretClient {
|
||||
return &SecuritySecretClient{config: c}
|
||||
}
|
||||
|
||||
// Use adds a list of mutation hooks to the hooks stack.
|
||||
// A call to `Use(f, g, h)` equals to `securitysecret.Hooks(f(g(h())))`.
|
||||
func (c *SecuritySecretClient) Use(hooks ...Hook) {
|
||||
c.hooks.SecuritySecret = append(c.hooks.SecuritySecret, hooks...)
|
||||
}
|
||||
|
||||
// Intercept adds a list of query interceptors to the interceptors stack.
|
||||
// A call to `Intercept(f, g, h)` equals to `securitysecret.Intercept(f(g(h())))`.
|
||||
func (c *SecuritySecretClient) Intercept(interceptors ...Interceptor) {
|
||||
c.inters.SecuritySecret = append(c.inters.SecuritySecret, interceptors...)
|
||||
}
|
||||
|
||||
// Create returns a builder for creating a SecuritySecret entity.
|
||||
func (c *SecuritySecretClient) Create() *SecuritySecretCreate {
|
||||
mutation := newSecuritySecretMutation(c.config, OpCreate)
|
||||
return &SecuritySecretCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// CreateBulk returns a builder for creating a bulk of SecuritySecret entities.
|
||||
func (c *SecuritySecretClient) CreateBulk(builders ...*SecuritySecretCreate) *SecuritySecretCreateBulk {
|
||||
return &SecuritySecretCreateBulk{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 *SecuritySecretClient) MapCreateBulk(slice any, setFunc func(*SecuritySecretCreate, int)) *SecuritySecretCreateBulk {
|
||||
rv := reflect.ValueOf(slice)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
return &SecuritySecretCreateBulk{err: fmt.Errorf("calling to SecuritySecretClient.MapCreateBulk with wrong type %T, need slice", slice)}
|
||||
}
|
||||
builders := make([]*SecuritySecretCreate, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
builders[i] = c.Create()
|
||||
setFunc(builders[i], i)
|
||||
}
|
||||
return &SecuritySecretCreateBulk{config: c.config, builders: builders}
|
||||
}
|
||||
|
||||
// Update returns an update builder for SecuritySecret.
|
||||
func (c *SecuritySecretClient) Update() *SecuritySecretUpdate {
|
||||
mutation := newSecuritySecretMutation(c.config, OpUpdate)
|
||||
return &SecuritySecretUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOne returns an update builder for the given entity.
|
||||
func (c *SecuritySecretClient) UpdateOne(_m *SecuritySecret) *SecuritySecretUpdateOne {
|
||||
mutation := newSecuritySecretMutation(c.config, OpUpdateOne, withSecuritySecret(_m))
|
||||
return &SecuritySecretUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// UpdateOneID returns an update builder for the given id.
|
||||
func (c *SecuritySecretClient) UpdateOneID(id int64) *SecuritySecretUpdateOne {
|
||||
mutation := newSecuritySecretMutation(c.config, OpUpdateOne, withSecuritySecretID(id))
|
||||
return &SecuritySecretUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// Delete returns a delete builder for SecuritySecret.
|
||||
func (c *SecuritySecretClient) Delete() *SecuritySecretDelete {
|
||||
mutation := newSecuritySecretMutation(c.config, OpDelete)
|
||||
return &SecuritySecretDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
|
||||
}
|
||||
|
||||
// DeleteOne returns a builder for deleting the given entity.
|
||||
func (c *SecuritySecretClient) DeleteOne(_m *SecuritySecret) *SecuritySecretDeleteOne {
|
||||
return c.DeleteOneID(_m.ID)
|
||||
}
|
||||
|
||||
// DeleteOneID returns a builder for deleting the given entity by its id.
|
||||
func (c *SecuritySecretClient) DeleteOneID(id int64) *SecuritySecretDeleteOne {
|
||||
builder := c.Delete().Where(securitysecret.ID(id))
|
||||
builder.mutation.id = &id
|
||||
builder.mutation.op = OpDeleteOne
|
||||
return &SecuritySecretDeleteOne{builder}
|
||||
}
|
||||
|
||||
// Query returns a query builder for SecuritySecret.
|
||||
func (c *SecuritySecretClient) Query() *SecuritySecretQuery {
|
||||
return &SecuritySecretQuery{
|
||||
config: c.config,
|
||||
ctx: &QueryContext{Type: TypeSecuritySecret},
|
||||
inters: c.Interceptors(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a SecuritySecret entity by its id.
|
||||
func (c *SecuritySecretClient) Get(ctx context.Context, id int64) (*SecuritySecret, error) {
|
||||
return c.Query().Where(securitysecret.ID(id)).Only(ctx)
|
||||
}
|
||||
|
||||
// GetX is like Get, but panics if an error occurs.
|
||||
func (c *SecuritySecretClient) GetX(ctx context.Context, id int64) *SecuritySecret {
|
||||
obj, err := c.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
// Hooks returns the client hooks.
|
||||
func (c *SecuritySecretClient) Hooks() []Hook {
|
||||
return c.hooks.SecuritySecret
|
||||
}
|
||||
|
||||
// Interceptors returns the client interceptors.
|
||||
func (c *SecuritySecretClient) Interceptors() []Interceptor {
|
||||
return c.inters.SecuritySecret
|
||||
}
|
||||
|
||||
func (c *SecuritySecretClient) mutate(ctx context.Context, m *SecuritySecretMutation) (Value, error) {
|
||||
switch m.Op() {
|
||||
case OpCreate:
|
||||
return (&SecuritySecretCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdate:
|
||||
return (&SecuritySecretUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpUpdateOne:
|
||||
return (&SecuritySecretUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
|
||||
case OpDelete, OpDeleteOne:
|
||||
return (&SecuritySecretDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("ent: unknown SecuritySecret mutation op: %q", m.Op())
|
||||
}
|
||||
}
|
||||
|
||||
// SettingClient is a client for the Setting schema.
|
||||
type SettingClient struct {
|
||||
config
|
||||
@@ -3607,13 +3748,13 @@ type (
|
||||
hooks struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||
ErrorPassthroughRule, Group, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
|
||||
Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Hook
|
||||
}
|
||||
inters struct {
|
||||
APIKey, Account, AccountGroup, Announcement, AnnouncementRead,
|
||||
ErrorPassthroughRule, Group, PromoCode, PromoCodeUsage, Proxy, RedeemCode,
|
||||
Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
SecuritySecret, Setting, UsageCleanupTask, UsageLog, User, UserAllowedGroup,
|
||||
UserAttributeDefinition, UserAttributeValue, UserSubscription []ent.Interceptor
|
||||
}
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||
@@ -102,6 +103,7 @@ func checkColumn(t, c string) error {
|
||||
promocodeusage.Table: promocodeusage.ValidColumn,
|
||||
proxy.Table: proxy.ValidColumn,
|
||||
redeemcode.Table: redeemcode.ValidColumn,
|
||||
securitysecret.Table: securitysecret.ValidColumn,
|
||||
setting.Table: setting.ValidColumn,
|
||||
usagecleanuptask.Table: usagecleanuptask.ValidColumn,
|
||||
usagelog.Table: usagelog.ValidColumn,
|
||||
|
||||
@@ -141,6 +141,18 @@ func (f RedeemCodeFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value,
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.RedeemCodeMutation", m)
|
||||
}
|
||||
|
||||
// The SecuritySecretFunc type is an adapter to allow the use of ordinary
|
||||
// function as SecuritySecret mutator.
|
||||
type SecuritySecretFunc func(context.Context, *ent.SecuritySecretMutation) (ent.Value, error)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f SecuritySecretFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
if mv, ok := m.(*ent.SecuritySecretMutation); ok {
|
||||
return f(ctx, mv)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.SecuritySecretMutation", m)
|
||||
}
|
||||
|
||||
// The SettingFunc type is an adapter to allow the use of ordinary
|
||||
// function as Setting mutator.
|
||||
type SettingFunc func(context.Context, *ent.SettingMutation) (ent.Value, error)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||
@@ -383,6 +384,33 @@ func (f TraverseRedeemCode) Traverse(ctx context.Context, q ent.Query) error {
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.RedeemCodeQuery", q)
|
||||
}
|
||||
|
||||
// The SecuritySecretFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type SecuritySecretFunc func(context.Context, *ent.SecuritySecretQuery) (ent.Value, error)
|
||||
|
||||
// Query calls f(ctx, q).
|
||||
func (f SecuritySecretFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
|
||||
if q, ok := q.(*ent.SecuritySecretQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected query type %T. expect *ent.SecuritySecretQuery", q)
|
||||
}
|
||||
|
||||
// The TraverseSecuritySecret type is an adapter to allow the use of ordinary function as Traverser.
|
||||
type TraverseSecuritySecret func(context.Context, *ent.SecuritySecretQuery) error
|
||||
|
||||
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
|
||||
func (f TraverseSecuritySecret) Intercept(next ent.Querier) ent.Querier {
|
||||
return next
|
||||
}
|
||||
|
||||
// Traverse calls f(ctx, q).
|
||||
func (f TraverseSecuritySecret) Traverse(ctx context.Context, q ent.Query) error {
|
||||
if q, ok := q.(*ent.SecuritySecretQuery); ok {
|
||||
return f(ctx, q)
|
||||
}
|
||||
return fmt.Errorf("unexpected query type %T. expect *ent.SecuritySecretQuery", q)
|
||||
}
|
||||
|
||||
// The SettingFunc type is an adapter to allow the use of ordinary function as a Querier.
|
||||
type SettingFunc func(context.Context, *ent.SettingQuery) (ent.Value, error)
|
||||
|
||||
@@ -624,6 +652,8 @@ func NewQuery(q ent.Query) (Query, error) {
|
||||
return &query[*ent.ProxyQuery, predicate.Proxy, proxy.OrderOption]{typ: ent.TypeProxy, tq: q}, nil
|
||||
case *ent.RedeemCodeQuery:
|
||||
return &query[*ent.RedeemCodeQuery, predicate.RedeemCode, redeemcode.OrderOption]{typ: ent.TypeRedeemCode, tq: q}, nil
|
||||
case *ent.SecuritySecretQuery:
|
||||
return &query[*ent.SecuritySecretQuery, predicate.SecuritySecret, securitysecret.OrderOption]{typ: ent.TypeSecuritySecret, tq: q}, nil
|
||||
case *ent.SettingQuery:
|
||||
return &query[*ent.SettingQuery, predicate.Setting, setting.OrderOption]{typ: ent.TypeSetting, tq: q}, nil
|
||||
case *ent.UsageCleanupTaskQuery:
|
||||
|
||||
@@ -575,6 +575,20 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
// SecuritySecretsColumns holds the columns for the "security_secrets" table.
|
||||
SecuritySecretsColumns = []*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: "key", Type: field.TypeString, Unique: true, Size: 100},
|
||||
{Name: "value", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||
}
|
||||
// SecuritySecretsTable holds the schema information for the "security_secrets" table.
|
||||
SecuritySecretsTable = &schema.Table{
|
||||
Name: "security_secrets",
|
||||
Columns: SecuritySecretsColumns,
|
||||
PrimaryKey: []*schema.Column{SecuritySecretsColumns[0]},
|
||||
}
|
||||
// SettingsColumns holds the columns for the "settings" table.
|
||||
SettingsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
@@ -1003,6 +1017,7 @@ var (
|
||||
PromoCodeUsagesTable,
|
||||
ProxiesTable,
|
||||
RedeemCodesTable,
|
||||
SecuritySecretsTable,
|
||||
SettingsTable,
|
||||
UsageCleanupTasksTable,
|
||||
UsageLogsTable,
|
||||
@@ -1059,6 +1074,9 @@ func init() {
|
||||
RedeemCodesTable.Annotation = &entsql.Annotation{
|
||||
Table: "redeem_codes",
|
||||
}
|
||||
SecuritySecretsTable.Annotation = &entsql.Annotation{
|
||||
Table: "security_secrets",
|
||||
}
|
||||
SettingsTable.Annotation = &entsql.Annotation{
|
||||
Table: "settings",
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/promocodeusage"
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||
@@ -55,6 +56,7 @@ const (
|
||||
TypePromoCodeUsage = "PromoCodeUsage"
|
||||
TypeProxy = "Proxy"
|
||||
TypeRedeemCode = "RedeemCode"
|
||||
TypeSecuritySecret = "SecuritySecret"
|
||||
TypeSetting = "Setting"
|
||||
TypeUsageCleanupTask = "UsageCleanupTask"
|
||||
TypeUsageLog = "UsageLog"
|
||||
@@ -13870,6 +13872,494 @@ func (m *RedeemCodeMutation) ResetEdge(name string) error {
|
||||
return fmt.Errorf("unknown RedeemCode edge %s", name)
|
||||
}
|
||||
|
||||
// SecuritySecretMutation represents an operation that mutates the SecuritySecret nodes in the graph.
|
||||
type SecuritySecretMutation struct {
|
||||
config
|
||||
op Op
|
||||
typ string
|
||||
id *int64
|
||||
created_at *time.Time
|
||||
updated_at *time.Time
|
||||
key *string
|
||||
value *string
|
||||
clearedFields map[string]struct{}
|
||||
done bool
|
||||
oldValue func(context.Context) (*SecuritySecret, error)
|
||||
predicates []predicate.SecuritySecret
|
||||
}
|
||||
|
||||
var _ ent.Mutation = (*SecuritySecretMutation)(nil)
|
||||
|
||||
// securitysecretOption allows management of the mutation configuration using functional options.
|
||||
type securitysecretOption func(*SecuritySecretMutation)
|
||||
|
||||
// newSecuritySecretMutation creates new mutation for the SecuritySecret entity.
|
||||
func newSecuritySecretMutation(c config, op Op, opts ...securitysecretOption) *SecuritySecretMutation {
|
||||
m := &SecuritySecretMutation{
|
||||
config: c,
|
||||
op: op,
|
||||
typ: TypeSecuritySecret,
|
||||
clearedFields: make(map[string]struct{}),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(m)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// withSecuritySecretID sets the ID field of the mutation.
|
||||
func withSecuritySecretID(id int64) securitysecretOption {
|
||||
return func(m *SecuritySecretMutation) {
|
||||
var (
|
||||
err error
|
||||
once sync.Once
|
||||
value *SecuritySecret
|
||||
)
|
||||
m.oldValue = func(ctx context.Context) (*SecuritySecret, error) {
|
||||
once.Do(func() {
|
||||
if m.done {
|
||||
err = errors.New("querying old values post mutation is not allowed")
|
||||
} else {
|
||||
value, err = m.Client().SecuritySecret.Get(ctx, id)
|
||||
}
|
||||
})
|
||||
return value, err
|
||||
}
|
||||
m.id = &id
|
||||
}
|
||||
}
|
||||
|
||||
// withSecuritySecret sets the old SecuritySecret of the mutation.
|
||||
func withSecuritySecret(node *SecuritySecret) securitysecretOption {
|
||||
return func(m *SecuritySecretMutation) {
|
||||
m.oldValue = func(context.Context) (*SecuritySecret, error) {
|
||||
return node, nil
|
||||
}
|
||||
m.id = &node.ID
|
||||
}
|
||||
}
|
||||
|
||||
// Client returns a new `ent.Client` from the mutation. If the mutation was
|
||||
// executed in a transaction (ent.Tx), a transactional client is returned.
|
||||
func (m SecuritySecretMutation) Client() *Client {
|
||||
client := &Client{config: m.config}
|
||||
client.init()
|
||||
return client
|
||||
}
|
||||
|
||||
// Tx returns an `ent.Tx` for mutations that were executed in transactions;
|
||||
// it returns an error otherwise.
|
||||
func (m SecuritySecretMutation) Tx() (*Tx, error) {
|
||||
if _, ok := m.driver.(*txDriver); !ok {
|
||||
return nil, errors.New("ent: mutation is not running in a transaction")
|
||||
}
|
||||
tx := &Tx{config: m.config}
|
||||
tx.init()
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// ID returns the ID value in the mutation. Note that the ID is only available
|
||||
// if it was provided to the builder or after it was returned from the database.
|
||||
func (m *SecuritySecretMutation) ID() (id int64, exists bool) {
|
||||
if m.id == nil {
|
||||
return
|
||||
}
|
||||
return *m.id, true
|
||||
}
|
||||
|
||||
// IDs queries the database and returns the entity ids that match the mutation's predicate.
|
||||
// That means, if the mutation is applied within a transaction with an isolation level such
|
||||
// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated
|
||||
// or updated by the mutation.
|
||||
func (m *SecuritySecretMutation) IDs(ctx context.Context) ([]int64, error) {
|
||||
switch {
|
||||
case m.op.Is(OpUpdateOne | OpDeleteOne):
|
||||
id, exists := m.ID()
|
||||
if exists {
|
||||
return []int64{id}, nil
|
||||
}
|
||||
fallthrough
|
||||
case m.op.Is(OpUpdate | OpDelete):
|
||||
return m.Client().SecuritySecret.Query().Where(m.predicates...).IDs(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op)
|
||||
}
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the "created_at" field.
|
||||
func (m *SecuritySecretMutation) SetCreatedAt(t time.Time) {
|
||||
m.created_at = &t
|
||||
}
|
||||
|
||||
// CreatedAt returns the value of the "created_at" field in the mutation.
|
||||
func (m *SecuritySecretMutation) CreatedAt() (r time.Time, exists bool) {
|
||||
v := m.created_at
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldCreatedAt returns the old "created_at" field's value of the SecuritySecret entity.
|
||||
// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *SecuritySecretMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldCreatedAt requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err)
|
||||
}
|
||||
return oldValue.CreatedAt, nil
|
||||
}
|
||||
|
||||
// ResetCreatedAt resets all changes to the "created_at" field.
|
||||
func (m *SecuritySecretMutation) ResetCreatedAt() {
|
||||
m.created_at = nil
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (m *SecuritySecretMutation) SetUpdatedAt(t time.Time) {
|
||||
m.updated_at = &t
|
||||
}
|
||||
|
||||
// UpdatedAt returns the value of the "updated_at" field in the mutation.
|
||||
func (m *SecuritySecretMutation) UpdatedAt() (r time.Time, exists bool) {
|
||||
v := m.updated_at
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldUpdatedAt returns the old "updated_at" field's value of the SecuritySecret entity.
|
||||
// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *SecuritySecretMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldUpdatedAt requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err)
|
||||
}
|
||||
return oldValue.UpdatedAt, nil
|
||||
}
|
||||
|
||||
// ResetUpdatedAt resets all changes to the "updated_at" field.
|
||||
func (m *SecuritySecretMutation) ResetUpdatedAt() {
|
||||
m.updated_at = nil
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (m *SecuritySecretMutation) SetKey(s string) {
|
||||
m.key = &s
|
||||
}
|
||||
|
||||
// Key returns the value of the "key" field in the mutation.
|
||||
func (m *SecuritySecretMutation) Key() (r string, exists bool) {
|
||||
v := m.key
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldKey returns the old "key" field's value of the SecuritySecret entity.
|
||||
// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *SecuritySecretMutation) OldKey(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldKey is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldKey requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldKey: %w", err)
|
||||
}
|
||||
return oldValue.Key, nil
|
||||
}
|
||||
|
||||
// ResetKey resets all changes to the "key" field.
|
||||
func (m *SecuritySecretMutation) ResetKey() {
|
||||
m.key = nil
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (m *SecuritySecretMutation) SetValue(s string) {
|
||||
m.value = &s
|
||||
}
|
||||
|
||||
// Value returns the value of the "value" field in the mutation.
|
||||
func (m *SecuritySecretMutation) Value() (r string, exists bool) {
|
||||
v := m.value
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldValue returns the old "value" field's value of the SecuritySecret entity.
|
||||
// If the SecuritySecret object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *SecuritySecretMutation) OldValue(ctx context.Context) (v string, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldValue is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldValue requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldValue: %w", err)
|
||||
}
|
||||
return oldValue.Value, nil
|
||||
}
|
||||
|
||||
// ResetValue resets all changes to the "value" field.
|
||||
func (m *SecuritySecretMutation) ResetValue() {
|
||||
m.value = nil
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the SecuritySecretMutation builder.
|
||||
func (m *SecuritySecretMutation) Where(ps ...predicate.SecuritySecret) {
|
||||
m.predicates = append(m.predicates, ps...)
|
||||
}
|
||||
|
||||
// WhereP appends storage-level predicates to the SecuritySecretMutation builder. Using this method,
|
||||
// users can use type-assertion to append predicates that do not depend on any generated package.
|
||||
func (m *SecuritySecretMutation) WhereP(ps ...func(*sql.Selector)) {
|
||||
p := make([]predicate.SecuritySecret, len(ps))
|
||||
for i := range ps {
|
||||
p[i] = ps[i]
|
||||
}
|
||||
m.Where(p...)
|
||||
}
|
||||
|
||||
// Op returns the operation name.
|
||||
func (m *SecuritySecretMutation) Op() Op {
|
||||
return m.op
|
||||
}
|
||||
|
||||
// SetOp allows setting the mutation operation.
|
||||
func (m *SecuritySecretMutation) SetOp(op Op) {
|
||||
m.op = op
|
||||
}
|
||||
|
||||
// Type returns the node type of this mutation (SecuritySecret).
|
||||
func (m *SecuritySecretMutation) Type() string {
|
||||
return m.typ
|
||||
}
|
||||
|
||||
// Fields returns all fields that were changed during this mutation. Note that in
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *SecuritySecretMutation) Fields() []string {
|
||||
fields := make([]string, 0, 4)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, securitysecret.FieldCreatedAt)
|
||||
}
|
||||
if m.updated_at != nil {
|
||||
fields = append(fields, securitysecret.FieldUpdatedAt)
|
||||
}
|
||||
if m.key != nil {
|
||||
fields = append(fields, securitysecret.FieldKey)
|
||||
}
|
||||
if m.value != nil {
|
||||
fields = append(fields, securitysecret.FieldValue)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// Field returns the value of a field with the given name. The second boolean
|
||||
// return value indicates that this field was not set, or was not defined in the
|
||||
// schema.
|
||||
func (m *SecuritySecretMutation) Field(name string) (ent.Value, bool) {
|
||||
switch name {
|
||||
case securitysecret.FieldCreatedAt:
|
||||
return m.CreatedAt()
|
||||
case securitysecret.FieldUpdatedAt:
|
||||
return m.UpdatedAt()
|
||||
case securitysecret.FieldKey:
|
||||
return m.Key()
|
||||
case securitysecret.FieldValue:
|
||||
return m.Value()
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// OldField returns the old value of the field from the database. An error is
|
||||
// returned if the mutation operation is not UpdateOne, or the query to the
|
||||
// database failed.
|
||||
func (m *SecuritySecretMutation) OldField(ctx context.Context, name string) (ent.Value, error) {
|
||||
switch name {
|
||||
case securitysecret.FieldCreatedAt:
|
||||
return m.OldCreatedAt(ctx)
|
||||
case securitysecret.FieldUpdatedAt:
|
||||
return m.OldUpdatedAt(ctx)
|
||||
case securitysecret.FieldKey:
|
||||
return m.OldKey(ctx)
|
||||
case securitysecret.FieldValue:
|
||||
return m.OldValue(ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown SecuritySecret field %s", name)
|
||||
}
|
||||
|
||||
// SetField sets the value of a field with the given name. It returns an error if
|
||||
// the field is not defined in the schema, or if the type mismatched the field
|
||||
// type.
|
||||
func (m *SecuritySecretMutation) SetField(name string, value ent.Value) error {
|
||||
switch name {
|
||||
case securitysecret.FieldCreatedAt:
|
||||
v, ok := value.(time.Time)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetCreatedAt(v)
|
||||
return nil
|
||||
case securitysecret.FieldUpdatedAt:
|
||||
v, ok := value.(time.Time)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetUpdatedAt(v)
|
||||
return nil
|
||||
case securitysecret.FieldKey:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetKey(v)
|
||||
return nil
|
||||
case securitysecret.FieldValue:
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetValue(v)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown SecuritySecret field %s", name)
|
||||
}
|
||||
|
||||
// AddedFields returns all numeric fields that were incremented/decremented during
|
||||
// this mutation.
|
||||
func (m *SecuritySecretMutation) AddedFields() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddedField returns the numeric value that was incremented/decremented on a field
|
||||
// with the given name. The second boolean return value indicates that this field
|
||||
// was not set, or was not defined in the schema.
|
||||
func (m *SecuritySecretMutation) AddedField(name string) (ent.Value, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// AddField adds the value to the field with the given name. It returns an error if
|
||||
// the field is not defined in the schema, or if the type mismatched the field
|
||||
// type.
|
||||
func (m *SecuritySecretMutation) AddField(name string, value ent.Value) error {
|
||||
switch name {
|
||||
}
|
||||
return fmt.Errorf("unknown SecuritySecret numeric field %s", name)
|
||||
}
|
||||
|
||||
// ClearedFields returns all nullable fields that were cleared during this
|
||||
// mutation.
|
||||
func (m *SecuritySecretMutation) ClearedFields() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FieldCleared returns a boolean indicating if a field with the given name was
|
||||
// cleared in this mutation.
|
||||
func (m *SecuritySecretMutation) FieldCleared(name string) bool {
|
||||
_, ok := m.clearedFields[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ClearField clears the value of the field with the given name. It returns an
|
||||
// error if the field is not defined in the schema.
|
||||
func (m *SecuritySecretMutation) ClearField(name string) error {
|
||||
return fmt.Errorf("unknown SecuritySecret nullable field %s", name)
|
||||
}
|
||||
|
||||
// ResetField resets all changes in the mutation for the field with the given name.
|
||||
// It returns an error if the field is not defined in the schema.
|
||||
func (m *SecuritySecretMutation) ResetField(name string) error {
|
||||
switch name {
|
||||
case securitysecret.FieldCreatedAt:
|
||||
m.ResetCreatedAt()
|
||||
return nil
|
||||
case securitysecret.FieldUpdatedAt:
|
||||
m.ResetUpdatedAt()
|
||||
return nil
|
||||
case securitysecret.FieldKey:
|
||||
m.ResetKey()
|
||||
return nil
|
||||
case securitysecret.FieldValue:
|
||||
m.ResetValue()
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("unknown SecuritySecret field %s", name)
|
||||
}
|
||||
|
||||
// AddedEdges returns all edge names that were set/added in this mutation.
|
||||
func (m *SecuritySecretMutation) AddedEdges() []string {
|
||||
edges := make([]string, 0, 0)
|
||||
return edges
|
||||
}
|
||||
|
||||
// AddedIDs returns all IDs (to other nodes) that were added for the given edge
|
||||
// name in this mutation.
|
||||
func (m *SecuritySecretMutation) AddedIDs(name string) []ent.Value {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovedEdges returns all edge names that were removed in this mutation.
|
||||
func (m *SecuritySecretMutation) RemovedEdges() []string {
|
||||
edges := make([]string, 0, 0)
|
||||
return edges
|
||||
}
|
||||
|
||||
// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with
|
||||
// the given name in this mutation.
|
||||
func (m *SecuritySecretMutation) RemovedIDs(name string) []ent.Value {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearedEdges returns all edge names that were cleared in this mutation.
|
||||
func (m *SecuritySecretMutation) ClearedEdges() []string {
|
||||
edges := make([]string, 0, 0)
|
||||
return edges
|
||||
}
|
||||
|
||||
// EdgeCleared returns a boolean which indicates if the edge with the given name
|
||||
// was cleared in this mutation.
|
||||
func (m *SecuritySecretMutation) EdgeCleared(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ClearEdge clears the value of the edge with the given name. It returns an error
|
||||
// if that edge is not defined in the schema.
|
||||
func (m *SecuritySecretMutation) ClearEdge(name string) error {
|
||||
return fmt.Errorf("unknown SecuritySecret unique edge %s", name)
|
||||
}
|
||||
|
||||
// ResetEdge resets all changes to the edge with the given name in this mutation.
|
||||
// It returns an error if the edge is not defined in the schema.
|
||||
func (m *SecuritySecretMutation) ResetEdge(name string) error {
|
||||
return fmt.Errorf("unknown SecuritySecret edge %s", name)
|
||||
}
|
||||
|
||||
// SettingMutation represents an operation that mutates the Setting nodes in the graph.
|
||||
type SettingMutation struct {
|
||||
config
|
||||
|
||||
@@ -39,6 +39,9 @@ type Proxy func(*sql.Selector)
|
||||
// RedeemCode is the predicate function for redeemcode builders.
|
||||
type RedeemCode func(*sql.Selector)
|
||||
|
||||
// SecuritySecret is the predicate function for securitysecret builders.
|
||||
type SecuritySecret func(*sql.Selector)
|
||||
|
||||
// Setting is the predicate function for setting builders.
|
||||
type Setting func(*sql.Selector)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/proxy"
|
||||
"github.com/Wei-Shaw/sub2api/ent/redeemcode"
|
||||
"github.com/Wei-Shaw/sub2api/ent/schema"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/ent/setting"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagecleanuptask"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usagelog"
|
||||
@@ -598,6 +599,43 @@ func init() {
|
||||
redeemcodeDescValidityDays := redeemcodeFields[9].Descriptor()
|
||||
// redeemcode.DefaultValidityDays holds the default value on creation for the validity_days field.
|
||||
redeemcode.DefaultValidityDays = redeemcodeDescValidityDays.Default.(int)
|
||||
securitysecretMixin := schema.SecuritySecret{}.Mixin()
|
||||
securitysecretMixinFields0 := securitysecretMixin[0].Fields()
|
||||
_ = securitysecretMixinFields0
|
||||
securitysecretFields := schema.SecuritySecret{}.Fields()
|
||||
_ = securitysecretFields
|
||||
// securitysecretDescCreatedAt is the schema descriptor for created_at field.
|
||||
securitysecretDescCreatedAt := securitysecretMixinFields0[0].Descriptor()
|
||||
// securitysecret.DefaultCreatedAt holds the default value on creation for the created_at field.
|
||||
securitysecret.DefaultCreatedAt = securitysecretDescCreatedAt.Default.(func() time.Time)
|
||||
// securitysecretDescUpdatedAt is the schema descriptor for updated_at field.
|
||||
securitysecretDescUpdatedAt := securitysecretMixinFields0[1].Descriptor()
|
||||
// securitysecret.DefaultUpdatedAt holds the default value on creation for the updated_at field.
|
||||
securitysecret.DefaultUpdatedAt = securitysecretDescUpdatedAt.Default.(func() time.Time)
|
||||
// securitysecret.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
|
||||
securitysecret.UpdateDefaultUpdatedAt = securitysecretDescUpdatedAt.UpdateDefault.(func() time.Time)
|
||||
// securitysecretDescKey is the schema descriptor for key field.
|
||||
securitysecretDescKey := securitysecretFields[0].Descriptor()
|
||||
// securitysecret.KeyValidator is a validator for the "key" field. It is called by the builders before save.
|
||||
securitysecret.KeyValidator = func() func(string) error {
|
||||
validators := securitysecretDescKey.Validators
|
||||
fns := [...]func(string) error{
|
||||
validators[0].(func(string) error),
|
||||
validators[1].(func(string) error),
|
||||
}
|
||||
return func(key string) error {
|
||||
for _, fn := range fns {
|
||||
if err := fn(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
// securitysecretDescValue is the schema descriptor for value field.
|
||||
securitysecretDescValue := securitysecretFields[1].Descriptor()
|
||||
// securitysecret.ValueValidator is a validator for the "value" field. It is called by the builders before save.
|
||||
securitysecret.ValueValidator = securitysecretDescValue.Validators[0].(func(string) error)
|
||||
settingFields := schema.Setting{}.Fields()
|
||||
_ = settingFields
|
||||
// settingDescKey is the schema descriptor for key field.
|
||||
|
||||
42
backend/ent/schema/security_secret.go
Normal file
42
backend/ent/schema/security_secret.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/entsql"
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
// SecuritySecret 存储系统级安全密钥(如 JWT 签名密钥、TOTP 加密密钥)。
|
||||
type SecuritySecret struct {
|
||||
ent.Schema
|
||||
}
|
||||
|
||||
func (SecuritySecret) Annotations() []schema.Annotation {
|
||||
return []schema.Annotation{
|
||||
entsql.Annotation{Table: "security_secrets"},
|
||||
}
|
||||
}
|
||||
|
||||
func (SecuritySecret) Mixin() []ent.Mixin {
|
||||
return []ent.Mixin{
|
||||
mixins.TimeMixin{},
|
||||
}
|
||||
}
|
||||
|
||||
func (SecuritySecret) Fields() []ent.Field {
|
||||
return []ent.Field{
|
||||
field.String("key").
|
||||
MaxLen(100).
|
||||
NotEmpty().
|
||||
Unique(),
|
||||
field.String("value").
|
||||
NotEmpty().
|
||||
SchemaType(map[string]string{
|
||||
dialect.Postgres: "text",
|
||||
}),
|
||||
}
|
||||
}
|
||||
139
backend/ent/securitysecret.go
Normal file
139
backend/ent/securitysecret.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// 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/securitysecret"
|
||||
)
|
||||
|
||||
// SecuritySecret is the model entity for the SecuritySecret schema.
|
||||
type SecuritySecret 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"`
|
||||
// Key holds the value of the "key" field.
|
||||
Key string `json:"key,omitempty"`
|
||||
// Value holds the value of the "value" field.
|
||||
Value string `json:"value,omitempty"`
|
||||
selectValues sql.SelectValues
|
||||
}
|
||||
|
||||
// scanValues returns the types for scanning values from sql.Rows.
|
||||
func (*SecuritySecret) scanValues(columns []string) ([]any, error) {
|
||||
values := make([]any, len(columns))
|
||||
for i := range columns {
|
||||
switch columns[i] {
|
||||
case securitysecret.FieldID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case securitysecret.FieldKey, securitysecret.FieldValue:
|
||||
values[i] = new(sql.NullString)
|
||||
case securitysecret.FieldCreatedAt, securitysecret.FieldUpdatedAt:
|
||||
values[i] = new(sql.NullTime)
|
||||
default:
|
||||
values[i] = new(sql.UnknownType)
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// assignValues assigns the values that were returned from sql.Rows (after scanning)
|
||||
// to the SecuritySecret fields.
|
||||
func (_m *SecuritySecret) 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 securitysecret.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 securitysecret.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 securitysecret.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 securitysecret.FieldKey:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field key", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Key = value.String
|
||||
}
|
||||
case securitysecret.FieldValue:
|
||||
if value, ok := values[i].(*sql.NullString); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field value", values[i])
|
||||
} else if value.Valid {
|
||||
_m.Value = value.String
|
||||
}
|
||||
default:
|
||||
_m.selectValues.Set(columns[i], values[i])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue returns the ent.Value that was dynamically selected and assigned to the SecuritySecret.
|
||||
// This includes values selected through modifiers, order, etc.
|
||||
func (_m *SecuritySecret) GetValue(name string) (ent.Value, error) {
|
||||
return _m.selectValues.Get(name)
|
||||
}
|
||||
|
||||
// Update returns a builder for updating this SecuritySecret.
|
||||
// Note that you need to call SecuritySecret.Unwrap() before calling this method if this SecuritySecret
|
||||
// was returned from a transaction, and the transaction was committed or rolled back.
|
||||
func (_m *SecuritySecret) Update() *SecuritySecretUpdateOne {
|
||||
return NewSecuritySecretClient(_m.config).UpdateOne(_m)
|
||||
}
|
||||
|
||||
// Unwrap unwraps the SecuritySecret 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 *SecuritySecret) Unwrap() *SecuritySecret {
|
||||
_tx, ok := _m.config.driver.(*txDriver)
|
||||
if !ok {
|
||||
panic("ent: SecuritySecret is not a transactional entity")
|
||||
}
|
||||
_m.config.driver = _tx.drv
|
||||
return _m
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer.
|
||||
func (_m *SecuritySecret) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("SecuritySecret(")
|
||||
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("key=")
|
||||
builder.WriteString(_m.Key)
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("value=")
|
||||
builder.WriteString(_m.Value)
|
||||
builder.WriteByte(')')
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// SecuritySecrets is a parsable slice of SecuritySecret.
|
||||
type SecuritySecrets []*SecuritySecret
|
||||
86
backend/ent/securitysecret/securitysecret.go
Normal file
86
backend/ent/securitysecret/securitysecret.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package securitysecret
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
)
|
||||
|
||||
const (
|
||||
// Label holds the string label denoting the securitysecret type in the database.
|
||||
Label = "security_secret"
|
||||
// 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"
|
||||
// FieldKey holds the string denoting the key field in the database.
|
||||
FieldKey = "key"
|
||||
// FieldValue holds the string denoting the value field in the database.
|
||||
FieldValue = "value"
|
||||
// Table holds the table name of the securitysecret in the database.
|
||||
Table = "security_secrets"
|
||||
)
|
||||
|
||||
// Columns holds all SQL columns for securitysecret fields.
|
||||
var Columns = []string{
|
||||
FieldID,
|
||||
FieldCreatedAt,
|
||||
FieldUpdatedAt,
|
||||
FieldKey,
|
||||
FieldValue,
|
||||
}
|
||||
|
||||
// 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
|
||||
// KeyValidator is a validator for the "key" field. It is called by the builders before save.
|
||||
KeyValidator func(string) error
|
||||
// ValueValidator is a validator for the "value" field. It is called by the builders before save.
|
||||
ValueValidator func(string) error
|
||||
)
|
||||
|
||||
// OrderOption defines the ordering options for the SecuritySecret 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()
|
||||
}
|
||||
|
||||
// ByKey orders the results by the key field.
|
||||
func ByKey(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldKey, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByValue orders the results by the value field.
|
||||
func ByValue(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldValue, opts...).ToFunc()
|
||||
}
|
||||
300
backend/ent/securitysecret/where.go
Normal file
300
backend/ent/securitysecret/where.go
Normal file
@@ -0,0 +1,300 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package securitysecret
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
)
|
||||
|
||||
// ID filters vertices based on their ID field.
|
||||
func ID(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDEQ applies the EQ predicate on the ID field.
|
||||
func IDEQ(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDNEQ applies the NEQ predicate on the ID field.
|
||||
func IDNEQ(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNEQ(FieldID, id))
|
||||
}
|
||||
|
||||
// IDIn applies the In predicate on the ID field.
|
||||
func IDIn(ids ...int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDNotIn applies the NotIn predicate on the ID field.
|
||||
func IDNotIn(ids ...int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNotIn(FieldID, ids...))
|
||||
}
|
||||
|
||||
// IDGT applies the GT predicate on the ID field.
|
||||
func IDGT(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDGTE applies the GTE predicate on the ID field.
|
||||
func IDGTE(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGTE(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLT applies the LT predicate on the ID field.
|
||||
func IDLT(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLT(FieldID, id))
|
||||
}
|
||||
|
||||
// IDLTE applies the LTE predicate on the ID field.
|
||||
func IDLTE(id int64) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(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.SecuritySecret {
|
||||
return predicate.SecuritySecret(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.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// Key applies equality check predicate on the "key" field. It's identical to KeyEQ.
|
||||
func Key(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldKey, v))
|
||||
}
|
||||
|
||||
// Value applies equality check predicate on the "value" field. It's identical to ValueEQ.
|
||||
func Value(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldValue, v))
|
||||
}
|
||||
|
||||
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
|
||||
func CreatedAtEQ(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
|
||||
func CreatedAtNEQ(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNEQ(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtIn applies the In predicate on the "created_at" field.
|
||||
func CreatedAtIn(vs ...time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
|
||||
func CreatedAtNotIn(vs ...time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNotIn(FieldCreatedAt, vs...))
|
||||
}
|
||||
|
||||
// CreatedAtGT applies the GT predicate on the "created_at" field.
|
||||
func CreatedAtGT(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
|
||||
func CreatedAtGTE(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLT applies the LT predicate on the "created_at" field.
|
||||
func CreatedAtLT(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLT(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
|
||||
func CreatedAtLTE(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLTE(FieldCreatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
|
||||
func UpdatedAtEQ(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
|
||||
func UpdatedAtNEQ(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNEQ(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtIn applies the In predicate on the "updated_at" field.
|
||||
func UpdatedAtIn(vs ...time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
|
||||
func UpdatedAtNotIn(vs ...time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNotIn(FieldUpdatedAt, vs...))
|
||||
}
|
||||
|
||||
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
|
||||
func UpdatedAtGT(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
|
||||
func UpdatedAtGTE(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
|
||||
func UpdatedAtLT(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLT(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
|
||||
func UpdatedAtLTE(v time.Time) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLTE(FieldUpdatedAt, v))
|
||||
}
|
||||
|
||||
// KeyEQ applies the EQ predicate on the "key" field.
|
||||
func KeyEQ(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyNEQ applies the NEQ predicate on the "key" field.
|
||||
func KeyNEQ(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNEQ(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyIn applies the In predicate on the "key" field.
|
||||
func KeyIn(vs ...string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldIn(FieldKey, vs...))
|
||||
}
|
||||
|
||||
// KeyNotIn applies the NotIn predicate on the "key" field.
|
||||
func KeyNotIn(vs ...string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNotIn(FieldKey, vs...))
|
||||
}
|
||||
|
||||
// KeyGT applies the GT predicate on the "key" field.
|
||||
func KeyGT(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGT(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyGTE applies the GTE predicate on the "key" field.
|
||||
func KeyGTE(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGTE(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyLT applies the LT predicate on the "key" field.
|
||||
func KeyLT(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLT(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyLTE applies the LTE predicate on the "key" field.
|
||||
func KeyLTE(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLTE(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyContains applies the Contains predicate on the "key" field.
|
||||
func KeyContains(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldContains(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyHasPrefix applies the HasPrefix predicate on the "key" field.
|
||||
func KeyHasPrefix(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldHasPrefix(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyHasSuffix applies the HasSuffix predicate on the "key" field.
|
||||
func KeyHasSuffix(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldHasSuffix(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyEqualFold applies the EqualFold predicate on the "key" field.
|
||||
func KeyEqualFold(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEqualFold(FieldKey, v))
|
||||
}
|
||||
|
||||
// KeyContainsFold applies the ContainsFold predicate on the "key" field.
|
||||
func KeyContainsFold(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldContainsFold(FieldKey, v))
|
||||
}
|
||||
|
||||
// ValueEQ applies the EQ predicate on the "value" field.
|
||||
func ValueEQ(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEQ(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueNEQ applies the NEQ predicate on the "value" field.
|
||||
func ValueNEQ(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNEQ(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueIn applies the In predicate on the "value" field.
|
||||
func ValueIn(vs ...string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldIn(FieldValue, vs...))
|
||||
}
|
||||
|
||||
// ValueNotIn applies the NotIn predicate on the "value" field.
|
||||
func ValueNotIn(vs ...string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldNotIn(FieldValue, vs...))
|
||||
}
|
||||
|
||||
// ValueGT applies the GT predicate on the "value" field.
|
||||
func ValueGT(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGT(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueGTE applies the GTE predicate on the "value" field.
|
||||
func ValueGTE(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldGTE(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueLT applies the LT predicate on the "value" field.
|
||||
func ValueLT(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLT(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueLTE applies the LTE predicate on the "value" field.
|
||||
func ValueLTE(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldLTE(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueContains applies the Contains predicate on the "value" field.
|
||||
func ValueContains(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldContains(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueHasPrefix applies the HasPrefix predicate on the "value" field.
|
||||
func ValueHasPrefix(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldHasPrefix(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueHasSuffix applies the HasSuffix predicate on the "value" field.
|
||||
func ValueHasSuffix(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldHasSuffix(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueEqualFold applies the EqualFold predicate on the "value" field.
|
||||
func ValueEqualFold(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldEqualFold(FieldValue, v))
|
||||
}
|
||||
|
||||
// ValueContainsFold applies the ContainsFold predicate on the "value" field.
|
||||
func ValueContainsFold(v string) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.FieldContainsFold(FieldValue, v))
|
||||
}
|
||||
|
||||
// And groups predicates with the AND operator between them.
|
||||
func And(predicates ...predicate.SecuritySecret) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.AndPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Or groups predicates with the OR operator between them.
|
||||
func Or(predicates ...predicate.SecuritySecret) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.OrPredicates(predicates...))
|
||||
}
|
||||
|
||||
// Not applies the not operator on the given predicate.
|
||||
func Not(p predicate.SecuritySecret) predicate.SecuritySecret {
|
||||
return predicate.SecuritySecret(sql.NotPredicates(p))
|
||||
}
|
||||
626
backend/ent/securitysecret_create.go
Normal file
626
backend/ent/securitysecret_create.go
Normal file
@@ -0,0 +1,626 @@
|
||||
// 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/securitysecret"
|
||||
)
|
||||
|
||||
// SecuritySecretCreate is the builder for creating a SecuritySecret entity.
|
||||
type SecuritySecretCreate struct {
|
||||
config
|
||||
mutation *SecuritySecretMutation
|
||||
hooks []Hook
|
||||
conflict []sql.ConflictOption
|
||||
}
|
||||
|
||||
// SetCreatedAt sets the "created_at" field.
|
||||
func (_c *SecuritySecretCreate) SetCreatedAt(v time.Time) *SecuritySecretCreate {
|
||||
_c.mutation.SetCreatedAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableCreatedAt sets the "created_at" field if the given value is not nil.
|
||||
func (_c *SecuritySecretCreate) SetNillableCreatedAt(v *time.Time) *SecuritySecretCreate {
|
||||
if v != nil {
|
||||
_c.SetCreatedAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_c *SecuritySecretCreate) SetUpdatedAt(v time.Time) *SecuritySecretCreate {
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil.
|
||||
func (_c *SecuritySecretCreate) SetNillableUpdatedAt(v *time.Time) *SecuritySecretCreate {
|
||||
if v != nil {
|
||||
_c.SetUpdatedAt(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (_c *SecuritySecretCreate) SetKey(v string) *SecuritySecretCreate {
|
||||
_c.mutation.SetKey(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (_c *SecuritySecretCreate) SetValue(v string) *SecuritySecretCreate {
|
||||
_c.mutation.SetValue(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Mutation returns the SecuritySecretMutation object of the builder.
|
||||
func (_c *SecuritySecretCreate) Mutation() *SecuritySecretMutation {
|
||||
return _c.mutation
|
||||
}
|
||||
|
||||
// Save creates the SecuritySecret in the database.
|
||||
func (_c *SecuritySecretCreate) Save(ctx context.Context) (*SecuritySecret, error) {
|
||||
_c.defaults()
|
||||
return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)
|
||||
}
|
||||
|
||||
// SaveX calls Save and panics if Save returns an error.
|
||||
func (_c *SecuritySecretCreate) SaveX(ctx context.Context) *SecuritySecret {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *SecuritySecretCreate) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *SecuritySecretCreate) 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 *SecuritySecretCreate) defaults() {
|
||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||
v := securitysecret.DefaultCreatedAt()
|
||||
_c.mutation.SetCreatedAt(v)
|
||||
}
|
||||
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
||||
v := securitysecret.DefaultUpdatedAt()
|
||||
_c.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_c *SecuritySecretCreate) check() error {
|
||||
if _, ok := _c.mutation.CreatedAt(); !ok {
|
||||
return &ValidationError{Name: "created_at", err: errors.New(`ent: missing required field "SecuritySecret.created_at"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.UpdatedAt(); !ok {
|
||||
return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "SecuritySecret.updated_at"`)}
|
||||
}
|
||||
if _, ok := _c.mutation.Key(); !ok {
|
||||
return &ValidationError{Name: "key", err: errors.New(`ent: missing required field "SecuritySecret.key"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Key(); ok {
|
||||
if err := securitysecret.KeyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.key": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.Value(); !ok {
|
||||
return &ValidationError{Name: "value", err: errors.New(`ent: missing required field "SecuritySecret.value"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.Value(); ok {
|
||||
if err := securitysecret.ValueValidator(v); err != nil {
|
||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.value": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_c *SecuritySecretCreate) sqlSave(ctx context.Context) (*SecuritySecret, 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 *SecuritySecretCreate) createSpec() (*SecuritySecret, *sqlgraph.CreateSpec) {
|
||||
var (
|
||||
_node = &SecuritySecret{config: _c.config}
|
||||
_spec = sqlgraph.NewCreateSpec(securitysecret.Table, sqlgraph.NewFieldSpec(securitysecret.FieldID, field.TypeInt64))
|
||||
)
|
||||
_spec.OnConflict = _c.conflict
|
||||
if value, ok := _c.mutation.CreatedAt(); ok {
|
||||
_spec.SetField(securitysecret.FieldCreatedAt, field.TypeTime, value)
|
||||
_node.CreatedAt = value
|
||||
}
|
||||
if value, ok := _c.mutation.UpdatedAt(); ok {
|
||||
_spec.SetField(securitysecret.FieldUpdatedAt, field.TypeTime, value)
|
||||
_node.UpdatedAt = value
|
||||
}
|
||||
if value, ok := _c.mutation.Key(); ok {
|
||||
_spec.SetField(securitysecret.FieldKey, field.TypeString, value)
|
||||
_node.Key = value
|
||||
}
|
||||
if value, ok := _c.mutation.Value(); ok {
|
||||
_spec.SetField(securitysecret.FieldValue, field.TypeString, value)
|
||||
_node.Value = value
|
||||
}
|
||||
return _node, _spec
|
||||
}
|
||||
|
||||
// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause
|
||||
// of the `INSERT` statement. For example:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// SetCreatedAt(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.SecuritySecretUpsert) {
|
||||
// SetCreatedAt(v+v).
|
||||
// }).
|
||||
// Exec(ctx)
|
||||
func (_c *SecuritySecretCreate) OnConflict(opts ...sql.ConflictOption) *SecuritySecretUpsertOne {
|
||||
_c.conflict = opts
|
||||
return &SecuritySecretUpsertOne{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||
// as conflict target. Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(sql.ConflictColumns(columns...)).
|
||||
// Exec(ctx)
|
||||
func (_c *SecuritySecretCreate) OnConflictColumns(columns ...string) *SecuritySecretUpsertOne {
|
||||
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||
return &SecuritySecretUpsertOne{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// SecuritySecretUpsertOne is the builder for "upsert"-ing
|
||||
// one SecuritySecret node.
|
||||
SecuritySecretUpsertOne struct {
|
||||
create *SecuritySecretCreate
|
||||
}
|
||||
|
||||
// SecuritySecretUpsert is the "OnConflict" setter.
|
||||
SecuritySecretUpsert struct {
|
||||
*sql.UpdateSet
|
||||
}
|
||||
)
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (u *SecuritySecretUpsert) SetUpdatedAt(v time.Time) *SecuritySecretUpsert {
|
||||
u.Set(securitysecret.FieldUpdatedAt, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsert) UpdateUpdatedAt() *SecuritySecretUpsert {
|
||||
u.SetExcluded(securitysecret.FieldUpdatedAt)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (u *SecuritySecretUpsert) SetKey(v string) *SecuritySecretUpsert {
|
||||
u.Set(securitysecret.FieldKey, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateKey sets the "key" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsert) UpdateKey() *SecuritySecretUpsert {
|
||||
u.SetExcluded(securitysecret.FieldKey)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (u *SecuritySecretUpsert) SetValue(v string) *SecuritySecretUpsert {
|
||||
u.Set(securitysecret.FieldValue, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateValue sets the "value" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsert) UpdateValue() *SecuritySecretUpsert {
|
||||
u.SetExcluded(securitysecret.FieldValue)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that were set on create.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// Exec(ctx)
|
||||
func (u *SecuritySecretUpsertOne) UpdateNewValues() *SecuritySecretUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
|
||||
if _, exists := u.create.mutation.CreatedAt(); exists {
|
||||
s.SetIgnore(securitysecret.FieldCreatedAt)
|
||||
}
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// Ignore sets each column to itself in case of conflict.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(sql.ResolveWithIgnore()).
|
||||
// Exec(ctx)
|
||||
func (u *SecuritySecretUpsertOne) Ignore() *SecuritySecretUpsertOne {
|
||||
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 *SecuritySecretUpsertOne) DoNothing() *SecuritySecretUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||
return u
|
||||
}
|
||||
|
||||
// Update allows overriding fields `UPDATE` values. See the SecuritySecretCreate.OnConflict
|
||||
// documentation for more info.
|
||||
func (u *SecuritySecretUpsertOne) Update(set func(*SecuritySecretUpsert)) *SecuritySecretUpsertOne {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||
set(&SecuritySecretUpsert{UpdateSet: update})
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (u *SecuritySecretUpsertOne) SetUpdatedAt(v time.Time) *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetUpdatedAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertOne) UpdateUpdatedAt() *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateUpdatedAt()
|
||||
})
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (u *SecuritySecretUpsertOne) SetKey(v string) *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetKey(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateKey sets the "key" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertOne) UpdateKey() *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateKey()
|
||||
})
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (u *SecuritySecretUpsertOne) SetValue(v string) *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetValue(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateValue sets the "value" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertOne) UpdateValue() *SecuritySecretUpsertOne {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateValue()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *SecuritySecretUpsertOne) Exec(ctx context.Context) error {
|
||||
if len(u.create.conflict) == 0 {
|
||||
return errors.New("ent: missing options for SecuritySecretCreate.OnConflict")
|
||||
}
|
||||
return u.create.Exec(ctx)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (u *SecuritySecretUpsertOne) 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 *SecuritySecretUpsertOne) 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 *SecuritySecretUpsertOne) IDX(ctx context.Context) int64 {
|
||||
id, err := u.ID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// SecuritySecretCreateBulk is the builder for creating many SecuritySecret entities in bulk.
|
||||
type SecuritySecretCreateBulk struct {
|
||||
config
|
||||
err error
|
||||
builders []*SecuritySecretCreate
|
||||
conflict []sql.ConflictOption
|
||||
}
|
||||
|
||||
// Save creates the SecuritySecret entities in the database.
|
||||
func (_c *SecuritySecretCreateBulk) Save(ctx context.Context) ([]*SecuritySecret, error) {
|
||||
if _c.err != nil {
|
||||
return nil, _c.err
|
||||
}
|
||||
specs := make([]*sqlgraph.CreateSpec, len(_c.builders))
|
||||
nodes := make([]*SecuritySecret, 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.(*SecuritySecretMutation)
|
||||
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 *SecuritySecretCreateBulk) SaveX(ctx context.Context) []*SecuritySecret {
|
||||
v, err := _c.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_c *SecuritySecretCreateBulk) Exec(ctx context.Context) error {
|
||||
_, err := _c.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_c *SecuritySecretCreateBulk) 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.SecuritySecret.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.SecuritySecretUpsert) {
|
||||
// SetCreatedAt(v+v).
|
||||
// }).
|
||||
// Exec(ctx)
|
||||
func (_c *SecuritySecretCreateBulk) OnConflict(opts ...sql.ConflictOption) *SecuritySecretUpsertBulk {
|
||||
_c.conflict = opts
|
||||
return &SecuritySecretUpsertBulk{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// OnConflictColumns calls `OnConflict` and configures the columns
|
||||
// as conflict target. Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(sql.ConflictColumns(columns...)).
|
||||
// Exec(ctx)
|
||||
func (_c *SecuritySecretCreateBulk) OnConflictColumns(columns ...string) *SecuritySecretUpsertBulk {
|
||||
_c.conflict = append(_c.conflict, sql.ConflictColumns(columns...))
|
||||
return &SecuritySecretUpsertBulk{
|
||||
create: _c,
|
||||
}
|
||||
}
|
||||
|
||||
// SecuritySecretUpsertBulk is the builder for "upsert"-ing
|
||||
// a bulk of SecuritySecret nodes.
|
||||
type SecuritySecretUpsertBulk struct {
|
||||
create *SecuritySecretCreateBulk
|
||||
}
|
||||
|
||||
// UpdateNewValues updates the mutable fields using the new values that
|
||||
// were set on create. Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(
|
||||
// sql.ResolveWithNewValues(),
|
||||
// ).
|
||||
// Exec(ctx)
|
||||
func (u *SecuritySecretUpsertBulk) UpdateNewValues() *SecuritySecretUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues())
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) {
|
||||
for _, b := range u.create.builders {
|
||||
if _, exists := b.mutation.CreatedAt(); exists {
|
||||
s.SetIgnore(securitysecret.FieldCreatedAt)
|
||||
}
|
||||
}
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// Ignore sets each column to itself in case of conflict.
|
||||
// Using this option is equivalent to using:
|
||||
//
|
||||
// client.SecuritySecret.Create().
|
||||
// OnConflict(sql.ResolveWithIgnore()).
|
||||
// Exec(ctx)
|
||||
func (u *SecuritySecretUpsertBulk) Ignore() *SecuritySecretUpsertBulk {
|
||||
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 *SecuritySecretUpsertBulk) DoNothing() *SecuritySecretUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.DoNothing())
|
||||
return u
|
||||
}
|
||||
|
||||
// Update allows overriding fields `UPDATE` values. See the SecuritySecretCreateBulk.OnConflict
|
||||
// documentation for more info.
|
||||
func (u *SecuritySecretUpsertBulk) Update(set func(*SecuritySecretUpsert)) *SecuritySecretUpsertBulk {
|
||||
u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) {
|
||||
set(&SecuritySecretUpsert{UpdateSet: update})
|
||||
}))
|
||||
return u
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (u *SecuritySecretUpsertBulk) SetUpdatedAt(v time.Time) *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetUpdatedAt(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertBulk) UpdateUpdatedAt() *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateUpdatedAt()
|
||||
})
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (u *SecuritySecretUpsertBulk) SetKey(v string) *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetKey(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateKey sets the "key" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertBulk) UpdateKey() *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateKey()
|
||||
})
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (u *SecuritySecretUpsertBulk) SetValue(v string) *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.SetValue(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateValue sets the "value" field to the value that was provided on create.
|
||||
func (u *SecuritySecretUpsertBulk) UpdateValue() *SecuritySecretUpsertBulk {
|
||||
return u.Update(func(s *SecuritySecretUpsert) {
|
||||
s.UpdateValue()
|
||||
})
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (u *SecuritySecretUpsertBulk) 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 SecuritySecretCreateBulk instead", i)
|
||||
}
|
||||
}
|
||||
if len(u.create.conflict) == 0 {
|
||||
return errors.New("ent: missing options for SecuritySecretCreateBulk.OnConflict")
|
||||
}
|
||||
return u.create.Exec(ctx)
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (u *SecuritySecretUpsertBulk) ExecX(ctx context.Context) {
|
||||
if err := u.create.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
88
backend/ent/securitysecret_delete.go
Normal file
88
backend/ent/securitysecret_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/predicate"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
)
|
||||
|
||||
// SecuritySecretDelete is the builder for deleting a SecuritySecret entity.
|
||||
type SecuritySecretDelete struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *SecuritySecretMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the SecuritySecretDelete builder.
|
||||
func (_d *SecuritySecretDelete) Where(ps ...predicate.SecuritySecret) *SecuritySecretDelete {
|
||||
_d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query and returns how many vertices were deleted.
|
||||
func (_d *SecuritySecretDelete) 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 *SecuritySecretDelete) ExecX(ctx context.Context) int {
|
||||
n, err := _d.Exec(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (_d *SecuritySecretDelete) sqlExec(ctx context.Context) (int, error) {
|
||||
_spec := sqlgraph.NewDeleteSpec(securitysecret.Table, sqlgraph.NewFieldSpec(securitysecret.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
|
||||
}
|
||||
|
||||
// SecuritySecretDeleteOne is the builder for deleting a single SecuritySecret entity.
|
||||
type SecuritySecretDeleteOne struct {
|
||||
_d *SecuritySecretDelete
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the SecuritySecretDelete builder.
|
||||
func (_d *SecuritySecretDeleteOne) Where(ps ...predicate.SecuritySecret) *SecuritySecretDeleteOne {
|
||||
_d._d.mutation.Where(ps...)
|
||||
return _d
|
||||
}
|
||||
|
||||
// Exec executes the deletion query.
|
||||
func (_d *SecuritySecretDeleteOne) Exec(ctx context.Context) error {
|
||||
n, err := _d._d.Exec(ctx)
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case n == 0:
|
||||
return &NotFoundError{securitysecret.Label}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_d *SecuritySecretDeleteOne) ExecX(ctx context.Context) {
|
||||
if err := _d.Exec(ctx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
564
backend/ent/securitysecret_query.go
Normal file
564
backend/ent/securitysecret_query.go
Normal file
@@ -0,0 +1,564 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"entgo.io/ent"
|
||||
"entgo.io/ent/dialect"
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
)
|
||||
|
||||
// SecuritySecretQuery is the builder for querying SecuritySecret entities.
|
||||
type SecuritySecretQuery struct {
|
||||
config
|
||||
ctx *QueryContext
|
||||
order []securitysecret.OrderOption
|
||||
inters []Interceptor
|
||||
predicates []predicate.SecuritySecret
|
||||
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 SecuritySecretQuery builder.
|
||||
func (_q *SecuritySecretQuery) Where(ps ...predicate.SecuritySecret) *SecuritySecretQuery {
|
||||
_q.predicates = append(_q.predicates, ps...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// Limit the number of records to be returned by this query.
|
||||
func (_q *SecuritySecretQuery) Limit(limit int) *SecuritySecretQuery {
|
||||
_q.ctx.Limit = &limit
|
||||
return _q
|
||||
}
|
||||
|
||||
// Offset to start from.
|
||||
func (_q *SecuritySecretQuery) Offset(offset int) *SecuritySecretQuery {
|
||||
_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 *SecuritySecretQuery) Unique(unique bool) *SecuritySecretQuery {
|
||||
_q.ctx.Unique = &unique
|
||||
return _q
|
||||
}
|
||||
|
||||
// Order specifies how the records should be ordered.
|
||||
func (_q *SecuritySecretQuery) Order(o ...securitysecret.OrderOption) *SecuritySecretQuery {
|
||||
_q.order = append(_q.order, o...)
|
||||
return _q
|
||||
}
|
||||
|
||||
// First returns the first SecuritySecret entity from the query.
|
||||
// Returns a *NotFoundError when no SecuritySecret was found.
|
||||
func (_q *SecuritySecretQuery) First(ctx context.Context) (*SecuritySecret, 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{securitysecret.Label}
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// FirstX is like First, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) FirstX(ctx context.Context) *SecuritySecret {
|
||||
node, err := _q.First(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// FirstID returns the first SecuritySecret ID from the query.
|
||||
// Returns a *NotFoundError when no SecuritySecret ID was found.
|
||||
func (_q *SecuritySecretQuery) 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{securitysecret.Label}
|
||||
return
|
||||
}
|
||||
return ids[0], nil
|
||||
}
|
||||
|
||||
// FirstIDX is like FirstID, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) FirstIDX(ctx context.Context) int64 {
|
||||
id, err := _q.FirstID(ctx)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// Only returns a single SecuritySecret entity found by the query, ensuring it only returns one.
|
||||
// Returns a *NotSingularError when more than one SecuritySecret entity is found.
|
||||
// Returns a *NotFoundError when no SecuritySecret entities are found.
|
||||
func (_q *SecuritySecretQuery) Only(ctx context.Context) (*SecuritySecret, 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{securitysecret.Label}
|
||||
default:
|
||||
return nil, &NotSingularError{securitysecret.Label}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyX is like Only, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) OnlyX(ctx context.Context) *SecuritySecret {
|
||||
node, err := _q.Only(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// OnlyID is like Only, but returns the only SecuritySecret ID in the query.
|
||||
// Returns a *NotSingularError when more than one SecuritySecret ID is found.
|
||||
// Returns a *NotFoundError when no entities are found.
|
||||
func (_q *SecuritySecretQuery) 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{securitysecret.Label}
|
||||
default:
|
||||
err = &NotSingularError{securitysecret.Label}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnlyIDX is like OnlyID, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) 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 SecuritySecrets.
|
||||
func (_q *SecuritySecretQuery) All(ctx context.Context) ([]*SecuritySecret, error) {
|
||||
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
|
||||
if err := _q.prepareQuery(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qr := querierAll[[]*SecuritySecret, *SecuritySecretQuery]()
|
||||
return withInterceptors[[]*SecuritySecret](ctx, _q, qr, _q.inters)
|
||||
}
|
||||
|
||||
// AllX is like All, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) AllX(ctx context.Context) []*SecuritySecret {
|
||||
nodes, err := _q.All(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// IDs executes the query and returns a list of SecuritySecret IDs.
|
||||
func (_q *SecuritySecretQuery) 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(securitysecret.FieldID).Scan(ctx, &ids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IDsX is like IDs, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) 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 *SecuritySecretQuery) 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[*SecuritySecretQuery](), _q.inters)
|
||||
}
|
||||
|
||||
// CountX is like Count, but panics if an error occurs.
|
||||
func (_q *SecuritySecretQuery) 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 *SecuritySecretQuery) 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 *SecuritySecretQuery) ExistX(ctx context.Context) bool {
|
||||
exist, err := _q.Exist(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the SecuritySecretQuery 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 *SecuritySecretQuery) Clone() *SecuritySecretQuery {
|
||||
if _q == nil {
|
||||
return nil
|
||||
}
|
||||
return &SecuritySecretQuery{
|
||||
config: _q.config,
|
||||
ctx: _q.ctx.Clone(),
|
||||
order: append([]securitysecret.OrderOption{}, _q.order...),
|
||||
inters: append([]Interceptor{}, _q.inters...),
|
||||
predicates: append([]predicate.SecuritySecret{}, _q.predicates...),
|
||||
// clone intermediate query.
|
||||
sql: _q.sql.Clone(),
|
||||
path: _q.path,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.SecuritySecret.Query().
|
||||
// GroupBy(securitysecret.FieldCreatedAt).
|
||||
// Aggregate(ent.Count()).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *SecuritySecretQuery) GroupBy(field string, fields ...string) *SecuritySecretGroupBy {
|
||||
_q.ctx.Fields = append([]string{field}, fields...)
|
||||
grbuild := &SecuritySecretGroupBy{build: _q}
|
||||
grbuild.flds = &_q.ctx.Fields
|
||||
grbuild.label = securitysecret.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.SecuritySecret.Query().
|
||||
// Select(securitysecret.FieldCreatedAt).
|
||||
// Scan(ctx, &v)
|
||||
func (_q *SecuritySecretQuery) Select(fields ...string) *SecuritySecretSelect {
|
||||
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
|
||||
sbuild := &SecuritySecretSelect{SecuritySecretQuery: _q}
|
||||
sbuild.label = securitysecret.Label
|
||||
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
|
||||
return sbuild
|
||||
}
|
||||
|
||||
// Aggregate returns a SecuritySecretSelect configured with the given aggregations.
|
||||
func (_q *SecuritySecretQuery) Aggregate(fns ...AggregateFunc) *SecuritySecretSelect {
|
||||
return _q.Select().Aggregate(fns...)
|
||||
}
|
||||
|
||||
func (_q *SecuritySecretQuery) 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 !securitysecret.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 *SecuritySecretQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SecuritySecret, error) {
|
||||
var (
|
||||
nodes = []*SecuritySecret{}
|
||||
_spec = _q.querySpec()
|
||||
)
|
||||
_spec.ScanValues = func(columns []string) ([]any, error) {
|
||||
return (*SecuritySecret).scanValues(nil, columns)
|
||||
}
|
||||
_spec.Assign = func(columns []string, values []any) error {
|
||||
node := &SecuritySecret{config: _q.config}
|
||||
nodes = append(nodes, node)
|
||||
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
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (_q *SecuritySecretQuery) 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 *SecuritySecretQuery) querySpec() *sqlgraph.QuerySpec {
|
||||
_spec := sqlgraph.NewQuerySpec(securitysecret.Table, securitysecret.Columns, sqlgraph.NewFieldSpec(securitysecret.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, securitysecret.FieldID)
|
||||
for i := range fields {
|
||||
if fields[i] != securitysecret.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 *SecuritySecretQuery) sqlQuery(ctx context.Context) *sql.Selector {
|
||||
builder := sql.Dialect(_q.driver.Dialect())
|
||||
t1 := builder.Table(securitysecret.Table)
|
||||
columns := _q.ctx.Fields
|
||||
if len(columns) == 0 {
|
||||
columns = securitysecret.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 *SecuritySecretQuery) ForUpdate(opts ...sql.LockOption) *SecuritySecretQuery {
|
||||
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 *SecuritySecretQuery) ForShare(opts ...sql.LockOption) *SecuritySecretQuery {
|
||||
if _q.driver.Dialect() == dialect.Postgres {
|
||||
_q.Unique(false)
|
||||
}
|
||||
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
|
||||
s.ForShare(opts...)
|
||||
})
|
||||
return _q
|
||||
}
|
||||
|
||||
// SecuritySecretGroupBy is the group-by builder for SecuritySecret entities.
|
||||
type SecuritySecretGroupBy struct {
|
||||
selector
|
||||
build *SecuritySecretQuery
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the group-by query.
|
||||
func (_g *SecuritySecretGroupBy) Aggregate(fns ...AggregateFunc) *SecuritySecretGroupBy {
|
||||
_g.fns = append(_g.fns, fns...)
|
||||
return _g
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_g *SecuritySecretGroupBy) 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[*SecuritySecretQuery, *SecuritySecretGroupBy](ctx, _g.build, _g, _g.build.inters, v)
|
||||
}
|
||||
|
||||
func (_g *SecuritySecretGroupBy) sqlScan(ctx context.Context, root *SecuritySecretQuery, 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)
|
||||
}
|
||||
|
||||
// SecuritySecretSelect is the builder for selecting fields of SecuritySecret entities.
|
||||
type SecuritySecretSelect struct {
|
||||
*SecuritySecretQuery
|
||||
selector
|
||||
}
|
||||
|
||||
// Aggregate adds the given aggregation functions to the selector query.
|
||||
func (_s *SecuritySecretSelect) Aggregate(fns ...AggregateFunc) *SecuritySecretSelect {
|
||||
_s.fns = append(_s.fns, fns...)
|
||||
return _s
|
||||
}
|
||||
|
||||
// Scan applies the selector query and scans the result into the given value.
|
||||
func (_s *SecuritySecretSelect) 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[*SecuritySecretQuery, *SecuritySecretSelect](ctx, _s.SecuritySecretQuery, _s, _s.inters, v)
|
||||
}
|
||||
|
||||
func (_s *SecuritySecretSelect) sqlScan(ctx context.Context, root *SecuritySecretQuery, 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)
|
||||
}
|
||||
316
backend/ent/securitysecret_update.go
Normal file
316
backend/ent/securitysecret_update.go
Normal file
@@ -0,0 +1,316 @@
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/dialect/sql/sqlgraph"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/Wei-Shaw/sub2api/ent/predicate"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
)
|
||||
|
||||
// SecuritySecretUpdate is the builder for updating SecuritySecret entities.
|
||||
type SecuritySecretUpdate struct {
|
||||
config
|
||||
hooks []Hook
|
||||
mutation *SecuritySecretMutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the SecuritySecretUpdate builder.
|
||||
func (_u *SecuritySecretUpdate) Where(ps ...predicate.SecuritySecret) *SecuritySecretUpdate {
|
||||
_u.mutation.Where(ps...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *SecuritySecretUpdate) SetUpdatedAt(v time.Time) *SecuritySecretUpdate {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (_u *SecuritySecretUpdate) SetKey(v string) *SecuritySecretUpdate {
|
||||
_u.mutation.SetKey(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableKey sets the "key" field if the given value is not nil.
|
||||
func (_u *SecuritySecretUpdate) SetNillableKey(v *string) *SecuritySecretUpdate {
|
||||
if v != nil {
|
||||
_u.SetKey(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (_u *SecuritySecretUpdate) SetValue(v string) *SecuritySecretUpdate {
|
||||
_u.mutation.SetValue(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableValue sets the "value" field if the given value is not nil.
|
||||
func (_u *SecuritySecretUpdate) SetNillableValue(v *string) *SecuritySecretUpdate {
|
||||
if v != nil {
|
||||
_u.SetValue(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// Mutation returns the SecuritySecretMutation object of the builder.
|
||||
func (_u *SecuritySecretUpdate) Mutation() *SecuritySecretMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// Save executes the query and returns the number of nodes affected by the update operation.
|
||||
func (_u *SecuritySecretUpdate) 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 *SecuritySecretUpdate) SaveX(ctx context.Context) int {
|
||||
affected, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return affected
|
||||
}
|
||||
|
||||
// Exec executes the query.
|
||||
func (_u *SecuritySecretUpdate) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *SecuritySecretUpdate) 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 *SecuritySecretUpdate) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := securitysecret.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *SecuritySecretUpdate) check() error {
|
||||
if v, ok := _u.mutation.Key(); ok {
|
||||
if err := securitysecret.KeyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.key": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Value(); ok {
|
||||
if err := securitysecret.ValueValidator(v); err != nil {
|
||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.value": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *SecuritySecretUpdate) sqlSave(ctx context.Context) (_node int, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(securitysecret.Table, securitysecret.Columns, sqlgraph.NewFieldSpec(securitysecret.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(securitysecret.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Key(); ok {
|
||||
_spec.SetField(securitysecret.FieldKey, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Value(); ok {
|
||||
_spec.SetField(securitysecret.FieldValue, field.TypeString, value)
|
||||
}
|
||||
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
|
||||
if _, ok := err.(*sqlgraph.NotFoundError); ok {
|
||||
err = &NotFoundError{securitysecret.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
|
||||
// SecuritySecretUpdateOne is the builder for updating a single SecuritySecret entity.
|
||||
type SecuritySecretUpdateOne struct {
|
||||
config
|
||||
fields []string
|
||||
hooks []Hook
|
||||
mutation *SecuritySecretMutation
|
||||
}
|
||||
|
||||
// SetUpdatedAt sets the "updated_at" field.
|
||||
func (_u *SecuritySecretUpdateOne) SetUpdatedAt(v time.Time) *SecuritySecretUpdateOne {
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetKey sets the "key" field.
|
||||
func (_u *SecuritySecretUpdateOne) SetKey(v string) *SecuritySecretUpdateOne {
|
||||
_u.mutation.SetKey(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableKey sets the "key" field if the given value is not nil.
|
||||
func (_u *SecuritySecretUpdateOne) SetNillableKey(v *string) *SecuritySecretUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetKey(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetValue sets the "value" field.
|
||||
func (_u *SecuritySecretUpdateOne) SetValue(v string) *SecuritySecretUpdateOne {
|
||||
_u.mutation.SetValue(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableValue sets the "value" field if the given value is not nil.
|
||||
func (_u *SecuritySecretUpdateOne) SetNillableValue(v *string) *SecuritySecretUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetValue(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// Mutation returns the SecuritySecretMutation object of the builder.
|
||||
func (_u *SecuritySecretUpdateOne) Mutation() *SecuritySecretMutation {
|
||||
return _u.mutation
|
||||
}
|
||||
|
||||
// Where appends a list predicates to the SecuritySecretUpdate builder.
|
||||
func (_u *SecuritySecretUpdateOne) Where(ps ...predicate.SecuritySecret) *SecuritySecretUpdateOne {
|
||||
_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 *SecuritySecretUpdateOne) Select(field string, fields ...string) *SecuritySecretUpdateOne {
|
||||
_u.fields = append([]string{field}, fields...)
|
||||
return _u
|
||||
}
|
||||
|
||||
// Save executes the query and returns the updated SecuritySecret entity.
|
||||
func (_u *SecuritySecretUpdateOne) Save(ctx context.Context) (*SecuritySecret, error) {
|
||||
_u.defaults()
|
||||
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
|
||||
}
|
||||
|
||||
// SaveX is like Save, but panics if an error occurs.
|
||||
func (_u *SecuritySecretUpdateOne) SaveX(ctx context.Context) *SecuritySecret {
|
||||
node, err := _u.Save(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Exec executes the query on the entity.
|
||||
func (_u *SecuritySecretUpdateOne) Exec(ctx context.Context) error {
|
||||
_, err := _u.Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecX is like Exec, but panics if an error occurs.
|
||||
func (_u *SecuritySecretUpdateOne) 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 *SecuritySecretUpdateOne) defaults() {
|
||||
if _, ok := _u.mutation.UpdatedAt(); !ok {
|
||||
v := securitysecret.UpdateDefaultUpdatedAt()
|
||||
_u.mutation.SetUpdatedAt(v)
|
||||
}
|
||||
}
|
||||
|
||||
// check runs all checks and user-defined validators on the builder.
|
||||
func (_u *SecuritySecretUpdateOne) check() error {
|
||||
if v, ok := _u.mutation.Key(); ok {
|
||||
if err := securitysecret.KeyValidator(v); err != nil {
|
||||
return &ValidationError{Name: "key", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.key": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.Value(); ok {
|
||||
if err := securitysecret.ValueValidator(v); err != nil {
|
||||
return &ValidationError{Name: "value", err: fmt.Errorf(`ent: validator failed for field "SecuritySecret.value": %w`, err)}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (_u *SecuritySecretUpdateOne) sqlSave(ctx context.Context) (_node *SecuritySecret, err error) {
|
||||
if err := _u.check(); err != nil {
|
||||
return _node, err
|
||||
}
|
||||
_spec := sqlgraph.NewUpdateSpec(securitysecret.Table, securitysecret.Columns, sqlgraph.NewFieldSpec(securitysecret.FieldID, field.TypeInt64))
|
||||
id, ok := _u.mutation.ID()
|
||||
if !ok {
|
||||
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "SecuritySecret.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, securitysecret.FieldID)
|
||||
for _, f := range fields {
|
||||
if !securitysecret.ValidColumn(f) {
|
||||
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
|
||||
}
|
||||
if f != securitysecret.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(securitysecret.FieldUpdatedAt, field.TypeTime, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Key(); ok {
|
||||
_spec.SetField(securitysecret.FieldKey, field.TypeString, value)
|
||||
}
|
||||
if value, ok := _u.mutation.Value(); ok {
|
||||
_spec.SetField(securitysecret.FieldValue, field.TypeString, value)
|
||||
}
|
||||
_node = &SecuritySecret{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{securitysecret.Label}
|
||||
} else if sqlgraph.IsConstraintError(err) {
|
||||
err = &ConstraintError{msg: err.Error(), wrap: err}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_u.mutation.done = true
|
||||
return _node, nil
|
||||
}
|
||||
@@ -36,6 +36,8 @@ type Tx struct {
|
||||
Proxy *ProxyClient
|
||||
// RedeemCode is the client for interacting with the RedeemCode builders.
|
||||
RedeemCode *RedeemCodeClient
|
||||
// SecuritySecret is the client for interacting with the SecuritySecret builders.
|
||||
SecuritySecret *SecuritySecretClient
|
||||
// Setting is the client for interacting with the Setting builders.
|
||||
Setting *SettingClient
|
||||
// UsageCleanupTask is the client for interacting with the UsageCleanupTask builders.
|
||||
@@ -194,6 +196,7 @@ func (tx *Tx) init() {
|
||||
tx.PromoCodeUsage = NewPromoCodeUsageClient(tx.config)
|
||||
tx.Proxy = NewProxyClient(tx.config)
|
||||
tx.RedeemCode = NewRedeemCodeClient(tx.config)
|
||||
tx.SecuritySecret = NewSecuritySecretClient(tx.config)
|
||||
tx.Setting = NewSettingClient(tx.config)
|
||||
tx.UsageCleanupTask = NewUsageCleanupTaskClient(tx.config)
|
||||
tx.UsageLog = NewUsageLogClient(tx.config)
|
||||
|
||||
@@ -680,7 +680,19 @@ func NormalizeRunMode(value string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Load 读取并校验完整配置(要求 jwt.secret 已显式提供)。
|
||||
func Load() (*Config, error) {
|
||||
return load(false)
|
||||
}
|
||||
|
||||
// LoadForBootstrap 读取启动阶段配置。
|
||||
//
|
||||
// 启动阶段允许 jwt.secret 先留空,后续由数据库初始化流程补齐并再次完整校验。
|
||||
func LoadForBootstrap() (*Config, error) {
|
||||
return load(true)
|
||||
}
|
||||
|
||||
func load(allowMissingJWTSecret bool) (*Config, error) {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
@@ -756,10 +768,20 @@ func Load() (*Config, error) {
|
||||
cfg.Totp.EncryptionKeyConfigured = true
|
||||
}
|
||||
|
||||
originalJWTSecret := cfg.JWT.Secret
|
||||
if allowMissingJWTSecret && originalJWTSecret == "" {
|
||||
// 启动阶段允许先无 JWT 密钥,后续在数据库初始化后补齐。
|
||||
cfg.JWT.Secret = strings.Repeat("0", 32)
|
||||
}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("validate config error: %w", err)
|
||||
}
|
||||
|
||||
if allowMissingJWTSecret && originalJWTSecret == "" {
|
||||
cfg.JWT.Secret = ""
|
||||
}
|
||||
|
||||
if !cfg.Security.URLAllowlist.Enabled {
|
||||
log.Println("Warning: security.url_allowlist.enabled=false; allowlist/SSRF checks disabled (minimal format validation only).")
|
||||
}
|
||||
|
||||
@@ -14,6 +14,19 @@ func resetViperWithJWTSecret(t *testing.T) {
|
||||
t.Setenv("JWT_SECRET", strings.Repeat("x", 32))
|
||||
}
|
||||
|
||||
func TestLoadForBootstrapAllowsMissingJWTSecret(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Setenv("JWT_SECRET", "")
|
||||
|
||||
cfg, err := LoadForBootstrap()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadForBootstrap() error: %v", err)
|
||||
}
|
||||
if cfg.JWT.Secret != "" {
|
||||
t.Fatalf("LoadForBootstrap() should keep empty jwt.secret during bootstrap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRunMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
@@ -9,5 +9,5 @@ var ProviderSet = wire.NewSet(
|
||||
|
||||
// ProvideConfig 提供应用配置
|
||||
func ProvideConfig() (*Config, error) {
|
||||
return Load()
|
||||
return LoadForBootstrap()
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ import (
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// OpenAIGatewayHandler handles OpenAI API gateway requests
|
||||
@@ -118,22 +116,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
|
||||
}
|
||||
reqStream := streamResult.Bool()
|
||||
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
isCodexCLI := openai.IsCodexCLIRequest(userAgent) || (h.cfg != nil && h.cfg.Gateway.ForceCodexCLI)
|
||||
if !isCodexCLI {
|
||||
existingInstructions := gjson.GetBytes(body, "instructions").String()
|
||||
if strings.TrimSpace(existingInstructions) == "" {
|
||||
if instructions := strings.TrimSpace(service.GetOpenCodeInstructions()); instructions != "" {
|
||||
newBody, err := sjson.SetBytes(body, "instructions", instructions)
|
||||
if err != nil {
|
||||
h.errorResponse(c, http.StatusInternalServerError, "api_error", "Failed to process request")
|
||||
return
|
||||
}
|
||||
body = newBody
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOpsRequestContext(c, reqModel, reqStream, body)
|
||||
|
||||
// 提前校验 function_call_output 是否具备可关联上下文,避免上游 400。
|
||||
|
||||
@@ -5,6 +5,7 @@ package repository
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/ent"
|
||||
@@ -66,6 +67,18 @@ func InitEnt(cfg *config.Config) (*ent.Client, *sql.DB, error) {
|
||||
// 创建 Ent 客户端,绑定到已配置的数据库驱动。
|
||||
client := ent.NewClient(ent.Driver(drv))
|
||||
|
||||
// 启动阶段:从配置或数据库中确保系统密钥可用。
|
||||
if err := ensureBootstrapSecrets(migrationCtx, client, cfg); err != nil {
|
||||
_ = client.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 在密钥补齐后执行完整配置校验,避免空 jwt.secret 导致服务运行时失败。
|
||||
if err := cfg.Validate(); err != nil {
|
||||
_ = client.Close()
|
||||
return nil, nil, fmt.Errorf("validate config after secret bootstrap: %w", err)
|
||||
}
|
||||
|
||||
// SIMPLE 模式:启动时补齐各平台默认分组。
|
||||
// - anthropic/openai/gemini: 确保存在 <platform>-default
|
||||
// - antigravity: 仅要求存在 >=2 个未软删除分组(用于 claude/gemini 混合调度场景)
|
||||
|
||||
@@ -48,6 +48,11 @@ func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) {
|
||||
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT to_regclass('public.settings')").Scan(&settingsRegclass))
|
||||
require.True(t, settingsRegclass.Valid, "expected settings table to exist")
|
||||
|
||||
// security_secrets table should exist
|
||||
var securitySecretsRegclass sql.NullString
|
||||
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT to_regclass('public.security_secrets')").Scan(&securitySecretsRegclass))
|
||||
require.True(t, securitySecretsRegclass.Valid, "expected security_secrets table to exist")
|
||||
|
||||
// user_allowed_groups table should exist
|
||||
var uagRegclass sql.NullString
|
||||
require.NoError(t, tx.QueryRowContext(context.Background(), "SELECT to_regclass('public.user_allowed_groups')").Scan(&uagRegclass))
|
||||
|
||||
108
backend/internal/repository/security_secret_bootstrap.go
Normal file
108
backend/internal/repository/security_secret_bootstrap.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
)
|
||||
|
||||
const securitySecretKeyJWT = "jwt_secret"
|
||||
|
||||
var readRandomBytes = rand.Read
|
||||
|
||||
func ensureBootstrapSecrets(ctx context.Context, client *ent.Client, cfg *config.Config) error {
|
||||
if client == nil {
|
||||
return fmt.Errorf("nil ent client")
|
||||
}
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("nil config")
|
||||
}
|
||||
|
||||
cfg.JWT.Secret = strings.TrimSpace(cfg.JWT.Secret)
|
||||
if cfg.JWT.Secret != "" {
|
||||
if err := createSecuritySecretIfAbsent(ctx, client, securitySecretKeyJWT, cfg.JWT.Secret); err != nil {
|
||||
return fmt.Errorf("persist jwt secret: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
secret, created, err := getOrCreateGeneratedSecuritySecret(ctx, client, securitySecretKeyJWT, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure jwt secret: %w", err)
|
||||
}
|
||||
cfg.JWT.Secret = secret
|
||||
|
||||
if created {
|
||||
log.Println("Warning: JWT secret auto-generated and persisted to database. Consider rotating to a managed secret for production.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOrCreateGeneratedSecuritySecret(ctx context.Context, client *ent.Client, key string, byteLength int) (string, bool, error) {
|
||||
existing, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(key)).Only(ctx)
|
||||
if err == nil {
|
||||
value := strings.TrimSpace(existing.Value)
|
||||
if len([]byte(value)) < 32 {
|
||||
return "", false, fmt.Errorf("stored secret %q must be at least 32 bytes", key)
|
||||
}
|
||||
return value, false, nil
|
||||
}
|
||||
if !ent.IsNotFound(err) {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
generated, err := generateHexSecret(byteLength)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
if err := client.SecuritySecret.Create().
|
||||
SetKey(key).
|
||||
SetValue(generated).
|
||||
OnConflictColumns(securitysecret.FieldKey).
|
||||
DoNothing().
|
||||
Exec(ctx); err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
stored, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(key)).Only(ctx)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
value := strings.TrimSpace(stored.Value)
|
||||
if len([]byte(value)) < 32 {
|
||||
return "", false, fmt.Errorf("stored secret %q must be at least 32 bytes", key)
|
||||
}
|
||||
return value, value == generated, nil
|
||||
}
|
||||
|
||||
func createSecuritySecretIfAbsent(ctx context.Context, client *ent.Client, key, value string) error {
|
||||
value = strings.TrimSpace(value)
|
||||
if len([]byte(value)) < 32 {
|
||||
return fmt.Errorf("secret %q must be at least 32 bytes", key)
|
||||
}
|
||||
|
||||
_, err := client.SecuritySecret.Create().SetKey(key).SetValue(value).Save(ctx)
|
||||
if err == nil || ent.IsConstraintError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func generateHexSecret(byteLength int) (string, error) {
|
||||
if byteLength <= 0 {
|
||||
byteLength = 32
|
||||
}
|
||||
buf := make([]byte, byteLength)
|
||||
if _, err := readRandomBytes(buf); err != nil {
|
||||
return "", fmt.Errorf("generate random secret: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(buf), nil
|
||||
}
|
||||
272
backend/internal/repository/security_secret_bootstrap_test.go
Normal file
272
backend/internal/repository/security_secret_bootstrap_test.go
Normal file
@@ -0,0 +1,272 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/enttest"
|
||||
"github.com/Wei-Shaw/sub2api/ent/securitysecret"
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"entgo.io/ent/dialect"
|
||||
entsql "entgo.io/ent/dialect/sql"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func newSecuritySecretTestClient(t *testing.T) *dbent.Client {
|
||||
t.Helper()
|
||||
name := strings.ReplaceAll(t.Name(), "/", "_")
|
||||
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared&_fk=1", name)
|
||||
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
|
||||
_, err = db.Exec("PRAGMA foreign_keys = ON")
|
||||
require.NoError(t, err)
|
||||
|
||||
drv := entsql.OpenDB(dialect.SQLite, db)
|
||||
client := enttest.NewClient(t, enttest.WithOptions(dbent.Driver(drv)))
|
||||
t.Cleanup(func() { _ = client.Close() })
|
||||
return client
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsNilInputs(t *testing.T) {
|
||||
err := ensureBootstrapSecrets(context.Background(), nil, &config.Config{})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "nil ent client")
|
||||
|
||||
client := newSecuritySecretTestClient(t)
|
||||
err = ensureBootstrapSecrets(context.Background(), client, nil)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "nil config")
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsGenerateAndPersistJWTSecret(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
cfg := &config.Config{}
|
||||
|
||||
err := ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, cfg.JWT.Secret)
|
||||
require.GreaterOrEqual(t, len([]byte(cfg.JWT.Secret)), 32)
|
||||
|
||||
stored, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(securitySecretKeyJWT)).Only(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.JWT.Secret, stored.Value)
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsLoadExistingJWTSecret(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
_, err := client.SecuritySecret.Create().SetKey(securitySecretKeyJWT).SetValue("existing-jwt-secret-32bytes-long!!!!").Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{}
|
||||
err = ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "existing-jwt-secret-32bytes-long!!!!", cfg.JWT.Secret)
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsRejectInvalidStoredSecret(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
_, err := client.SecuritySecret.Create().SetKey(securitySecretKeyJWT).SetValue("too-short").Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{}
|
||||
err = ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "at least 32 bytes")
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsPersistConfiguredJWTSecret(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
cfg := &config.Config{
|
||||
JWT: config.JWTConfig{Secret: "configured-jwt-secret-32bytes-long!!"},
|
||||
}
|
||||
|
||||
err := ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
stored, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(securitySecretKeyJWT)).Only(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "configured-jwt-secret-32bytes-long!!", stored.Value)
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsConfiguredSecretTooShort(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
cfg := &config.Config{JWT: config.JWTConfig{Secret: "short"}}
|
||||
|
||||
err := ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "at least 32 bytes")
|
||||
}
|
||||
|
||||
func TestEnsureBootstrapSecretsConfiguredSecretDuplicateIgnored(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
_, err := client.SecuritySecret.Create().
|
||||
SetKey(securitySecretKeyJWT).
|
||||
SetValue("existing-jwt-secret-32bytes-long!!!!").
|
||||
Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{JWT: config.JWTConfig{Secret: "another-configured-jwt-secret-32!!!!"}}
|
||||
err = ensureBootstrapSecrets(context.Background(), client, cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
stored, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(securitySecretKeyJWT)).Only(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "existing-jwt-secret-32bytes-long!!!!", stored.Value)
|
||||
}
|
||||
|
||||
func TestGetOrCreateGeneratedSecuritySecretTrimmedExistingValue(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
_, err := client.SecuritySecret.Create().
|
||||
SetKey("trimmed_key").
|
||||
SetValue(" existing-trimmed-secret-32bytes-long!! ").
|
||||
Save(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
value, created, err := getOrCreateGeneratedSecuritySecret(context.Background(), client, "trimmed_key", 32)
|
||||
require.NoError(t, err)
|
||||
require.False(t, created)
|
||||
require.Equal(t, "existing-trimmed-secret-32bytes-long!!", value)
|
||||
}
|
||||
|
||||
func TestGetOrCreateGeneratedSecuritySecretQueryError(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
require.NoError(t, client.Close())
|
||||
|
||||
_, _, err := getOrCreateGeneratedSecuritySecret(context.Background(), client, "closed_client_key", 32)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetOrCreateGeneratedSecuritySecretCreateValidationError(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
tooLongKey := strings.Repeat("k", 101)
|
||||
|
||||
_, _, err := getOrCreateGeneratedSecuritySecret(context.Background(), client, tooLongKey, 32)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetOrCreateGeneratedSecuritySecretConcurrentCreation(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
const goroutines = 8
|
||||
key := "concurrent_bootstrap_key"
|
||||
|
||||
values := make([]string, goroutines)
|
||||
createdFlags := make([]bool, goroutines)
|
||||
errs := make([]error, goroutines)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < goroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
values[idx], createdFlags[idx], errs[idx] = getOrCreateGeneratedSecuritySecret(context.Background(), client, key, 32)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i := range errs {
|
||||
require.NoError(t, errs[i])
|
||||
require.NotEmpty(t, values[i])
|
||||
}
|
||||
for i := 1; i < len(values); i++ {
|
||||
require.Equal(t, values[0], values[i])
|
||||
}
|
||||
|
||||
createdCount := 0
|
||||
for _, created := range createdFlags {
|
||||
if created {
|
||||
createdCount++
|
||||
}
|
||||
}
|
||||
require.GreaterOrEqual(t, createdCount, 1)
|
||||
require.LessOrEqual(t, createdCount, 1)
|
||||
|
||||
count, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ(key)).Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, count)
|
||||
}
|
||||
|
||||
func TestGetOrCreateGeneratedSecuritySecretGenerateError(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
originalRead := readRandomBytes
|
||||
readRandomBytes = func([]byte) (int, error) {
|
||||
return 0, errors.New("boom")
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
readRandomBytes = originalRead
|
||||
})
|
||||
|
||||
_, _, err := getOrCreateGeneratedSecuritySecret(context.Background(), client, "gen_error_key", 32)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "boom")
|
||||
}
|
||||
|
||||
func TestCreateSecuritySecretIfAbsent(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
|
||||
err := createSecuritySecretIfAbsent(context.Background(), client, "abc", "short")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "at least 32 bytes")
|
||||
|
||||
err = createSecuritySecretIfAbsent(context.Background(), client, "abc", "valid-jwt-secret-value-32bytes-long")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = createSecuritySecretIfAbsent(context.Background(), client, "abc", "another-valid-secret-value-32bytes")
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := client.SecuritySecret.Query().Where(securitysecret.KeyEQ("abc")).Count(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, count)
|
||||
}
|
||||
|
||||
func TestCreateSecuritySecretIfAbsentValidationError(t *testing.T) {
|
||||
client := newSecuritySecretTestClient(t)
|
||||
err := createSecuritySecretIfAbsent(
|
||||
context.Background(),
|
||||
client,
|
||||
strings.Repeat("k", 101),
|
||||
"valid-jwt-secret-value-32bytes-long",
|
||||
)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGenerateHexSecretReadError(t *testing.T) {
|
||||
originalRead := readRandomBytes
|
||||
readRandomBytes = func([]byte) (int, error) {
|
||||
return 0, errors.New("read random failed")
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
readRandomBytes = originalRead
|
||||
})
|
||||
|
||||
_, err := generateHexSecret(32)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "read random failed")
|
||||
}
|
||||
|
||||
func TestGenerateHexSecretLengths(t *testing.T) {
|
||||
v1, err := generateHexSecret(0)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, v1, 64)
|
||||
_, err = hex.DecodeString(v1)
|
||||
require.NoError(t, err)
|
||||
|
||||
v2, err := generateHexSecret(16)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, v2, 32)
|
||||
_, err = hex.DecodeString(v2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEqual(t, v1, v2)
|
||||
}
|
||||
@@ -696,23 +696,27 @@ func (a *Account) IsMixedSchedulingEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOpenAIOAuthPassthroughEnabled 返回 OpenAI OAuth 账号是否启用“原样透传(仅替换认证)”。
|
||||
// IsOpenAIPassthroughEnabled 返回 OpenAI 账号是否启用“自动透传(仅替换认证)”。
|
||||
//
|
||||
// 存储位置:accounts.extra.openai_oauth_passthrough。
|
||||
// 新字段:accounts.extra.openai_passthrough。
|
||||
// 兼容字段:accounts.extra.openai_oauth_passthrough(历史 OAuth 开关)。
|
||||
// 字段缺失或类型不正确时,按 false(关闭)处理。
|
||||
func (a *Account) IsOpenAIPassthroughEnabled() bool {
|
||||
if a == nil || !a.IsOpenAI() || a.Extra == nil {
|
||||
return false
|
||||
}
|
||||
if enabled, ok := a.Extra["openai_passthrough"].(bool); ok {
|
||||
return enabled
|
||||
}
|
||||
if enabled, ok := a.Extra["openai_oauth_passthrough"].(bool); ok {
|
||||
return enabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOpenAIOAuthPassthroughEnabled 兼容旧接口,等价于 OAuth 账号的 IsOpenAIPassthroughEnabled。
|
||||
func (a *Account) IsOpenAIOAuthPassthroughEnabled() bool {
|
||||
if a == nil || a.Extra == nil {
|
||||
return false
|
||||
}
|
||||
v, ok := a.Extra["openai_oauth_passthrough"]
|
||||
if !ok || v == nil {
|
||||
return false
|
||||
}
|
||||
enabled, ok := v.(bool)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return enabled
|
||||
return a != nil && a.IsOpenAIOAuth() && a.IsOpenAIPassthroughEnabled()
|
||||
}
|
||||
|
||||
// WindowCostSchedulability 窗口费用调度状态
|
||||
|
||||
72
backend/internal/service/account_openai_passthrough_test.go
Normal file
72
backend/internal/service/account_openai_passthrough_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAccount_IsOpenAIPassthroughEnabled(t *testing.T) {
|
||||
t.Run("新字段开启", func(t *testing.T) {
|
||||
account := &Account{
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeAPIKey,
|
||||
Extra: map[string]any{
|
||||
"openai_passthrough": true,
|
||||
},
|
||||
}
|
||||
require.True(t, account.IsOpenAIPassthroughEnabled())
|
||||
})
|
||||
|
||||
t.Run("兼容旧字段", func(t *testing.T) {
|
||||
account := &Account{
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeOAuth,
|
||||
Extra: map[string]any{
|
||||
"openai_oauth_passthrough": true,
|
||||
},
|
||||
}
|
||||
require.True(t, account.IsOpenAIPassthroughEnabled())
|
||||
})
|
||||
|
||||
t.Run("非OpenAI账号始终关闭", func(t *testing.T) {
|
||||
account := &Account{
|
||||
Platform: PlatformAnthropic,
|
||||
Type: AccountTypeOAuth,
|
||||
Extra: map[string]any{
|
||||
"openai_passthrough": true,
|
||||
},
|
||||
}
|
||||
require.False(t, account.IsOpenAIPassthroughEnabled())
|
||||
})
|
||||
|
||||
t.Run("空额外配置默认关闭", func(t *testing.T) {
|
||||
account := &Account{
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeOAuth,
|
||||
}
|
||||
require.False(t, account.IsOpenAIPassthroughEnabled())
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccount_IsOpenAIOAuthPassthroughEnabled(t *testing.T) {
|
||||
t.Run("仅OAuth类型允许返回开启", func(t *testing.T) {
|
||||
oauthAccount := &Account{
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeOAuth,
|
||||
Extra: map[string]any{
|
||||
"openai_passthrough": true,
|
||||
},
|
||||
}
|
||||
require.True(t, oauthAccount.IsOpenAIOAuthPassthroughEnabled())
|
||||
|
||||
apiKeyAccount := &Account{
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeAPIKey,
|
||||
Extra: map[string]any{
|
||||
"openai_passthrough": true,
|
||||
},
|
||||
}
|
||||
require.False(t, apiKeyAccount.IsOpenAIOAuthPassthroughEnabled())
|
||||
})
|
||||
}
|
||||
@@ -747,11 +747,11 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
originalModel := reqModel
|
||||
|
||||
isCodexCLI := openai.IsCodexCLIRequest(c.GetHeader("User-Agent")) || (s.cfg != nil && s.cfg.Gateway.ForceCodexCLI)
|
||||
passthroughEnabled := account.Type == AccountTypeOAuth && account.IsOpenAIOAuthPassthroughEnabled() && isCodexCLI
|
||||
passthroughEnabled := account.IsOpenAIPassthroughEnabled()
|
||||
if passthroughEnabled {
|
||||
// 透传分支只需要轻量提取字段,避免热路径全量 Unmarshal。
|
||||
reasoningEffort := extractOpenAIReasoningEffortFromBody(body, reqModel)
|
||||
return s.forwardOAuthPassthrough(ctx, c, account, originalBody, reqModel, reasoningEffort, reqStream, startTime)
|
||||
return s.forwardOpenAIPassthrough(ctx, c, account, originalBody, reqModel, reasoningEffort, reqStream, startTime)
|
||||
}
|
||||
|
||||
reqBody, err := getOpenAIRequestBodyMap(c, body)
|
||||
@@ -775,6 +775,14 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
// Track if body needs re-serialization
|
||||
bodyModified := false
|
||||
|
||||
// 非透传模式下,保持历史行为:非 Codex CLI 请求在 instructions 为空时注入默认指令。
|
||||
if !isCodexCLI && isInstructionsEmpty(reqBody) {
|
||||
if instructions := strings.TrimSpace(GetOpenCodeInstructions()); instructions != "" {
|
||||
reqBody["instructions"] = instructions
|
||||
bodyModified = true
|
||||
}
|
||||
}
|
||||
|
||||
// 对所有请求执行模型映射(包含 Codex CLI)。
|
||||
mappedModel := account.GetMappedModel(reqModel)
|
||||
if mappedModel != reqModel {
|
||||
@@ -994,7 +1002,7 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) forwardOAuthPassthrough(
|
||||
func (s *OpenAIGatewayService) forwardOpenAIPassthrough(
|
||||
ctx context.Context,
|
||||
c *gin.Context,
|
||||
account *Account,
|
||||
@@ -1004,7 +1012,14 @@ func (s *OpenAIGatewayService) forwardOAuthPassthrough(
|
||||
reqStream bool,
|
||||
startTime time.Time,
|
||||
) (*OpenAIForwardResult, error) {
|
||||
log.Printf("[OpenAI 透传] 已启用:account=%d name=%s", account.ID, account.Name)
|
||||
log.Printf(
|
||||
"[OpenAI 自动透传] 命中自动透传分支: account=%d name=%s type=%s model=%s stream=%v",
|
||||
account.ID,
|
||||
account.Name,
|
||||
account.Type,
|
||||
reqModel,
|
||||
reqStream,
|
||||
)
|
||||
|
||||
// Get access token
|
||||
token, _, err := s.GetAccessToken(ctx, account)
|
||||
@@ -1012,7 +1027,7 @@ func (s *OpenAIGatewayService) forwardOAuthPassthrough(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upstreamReq, err := s.buildUpstreamRequestOAuthPassthrough(ctx, c, account, body, token)
|
||||
upstreamReq, err := s.buildUpstreamRequestOpenAIPassthrough(ctx, c, account, body, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1092,14 +1107,29 @@ func (s *OpenAIGatewayService) forwardOAuthPassthrough(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) buildUpstreamRequestOAuthPassthrough(
|
||||
func (s *OpenAIGatewayService) buildUpstreamRequestOpenAIPassthrough(
|
||||
ctx context.Context,
|
||||
c *gin.Context,
|
||||
account *Account,
|
||||
body []byte,
|
||||
token string,
|
||||
) (*http.Request, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, chatgptCodexURL, bytes.NewReader(body))
|
||||
targetURL := openaiPlatformAPIURL
|
||||
switch account.Type {
|
||||
case AccountTypeOAuth:
|
||||
targetURL = chatgptCodexURL
|
||||
case AccountTypeAPIKey:
|
||||
baseURL := account.GetOpenAIBaseURL()
|
||||
if baseURL != "" {
|
||||
validatedURL, err := s.validateUpstreamBaseURL(baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetURL = buildOpenAIResponsesURL(validatedURL)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, targetURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1123,16 +1153,18 @@ func (s *OpenAIGatewayService) buildUpstreamRequestOAuthPassthrough(
|
||||
req.Header.Del("x-goog-api-key")
|
||||
req.Header.Set("authorization", "Bearer "+token)
|
||||
|
||||
// ChatGPT internal Codex API 必要头
|
||||
req.Host = "chatgpt.com"
|
||||
if chatgptAccountID := account.GetChatGPTAccountID(); chatgptAccountID != "" {
|
||||
req.Header.Set("chatgpt-account-id", chatgptAccountID)
|
||||
}
|
||||
if req.Header.Get("OpenAI-Beta") == "" {
|
||||
req.Header.Set("OpenAI-Beta", "responses=experimental")
|
||||
}
|
||||
if req.Header.Get("originator") == "" {
|
||||
req.Header.Set("originator", "codex_cli_rs")
|
||||
// OAuth 透传到 ChatGPT internal API 时补齐必要头。
|
||||
if account.Type == AccountTypeOAuth {
|
||||
req.Host = "chatgpt.com"
|
||||
if chatgptAccountID := account.GetChatGPTAccountID(); chatgptAccountID != "" {
|
||||
req.Header.Set("chatgpt-account-id", chatgptAccountID)
|
||||
}
|
||||
if req.Header.Get("OpenAI-Beta") == "" {
|
||||
req.Header.Set("OpenAI-Beta", "responses=experimental")
|
||||
}
|
||||
if req.Header.Get("originator") == "" {
|
||||
req.Header.Set("originator", "codex_cli_rs")
|
||||
}
|
||||
}
|
||||
|
||||
if req.Header.Get("content-type") == "" {
|
||||
@@ -1389,7 +1421,7 @@ func (s *OpenAIGatewayService) buildUpstreamRequest(ctx context.Context, c *gin.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetURL = validatedURL + "/responses"
|
||||
targetURL = buildOpenAIResponsesURL(validatedURL)
|
||||
}
|
||||
default:
|
||||
targetURL = openaiPlatformAPIURL
|
||||
@@ -2084,6 +2116,21 @@ func (s *OpenAIGatewayService) validateUpstreamBaseURL(raw string) (string, erro
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// buildOpenAIResponsesURL 组装 OpenAI Responses 端点。
|
||||
// - base 以 /v1 结尾:追加 /responses
|
||||
// - base 已是 /responses:原样返回
|
||||
// - 其他情况:追加 /v1/responses
|
||||
func buildOpenAIResponsesURL(base string) string {
|
||||
normalized := strings.TrimRight(strings.TrimSpace(base), "/")
|
||||
if strings.HasSuffix(normalized, "/responses") {
|
||||
return normalized
|
||||
}
|
||||
if strings.HasSuffix(normalized, "/v1") {
|
||||
return normalized + "/responses"
|
||||
}
|
||||
return normalized + "/v1/responses"
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) replaceModelInResponseBody(body []byte, fromModel, toModel string) []byte {
|
||||
// 使用 gjson/sjson 精确替换 model 字段,避免全量 JSON 反序列化
|
||||
if m := gjson.GetBytes(body, "model"); m.Exists() && m.Str == fromModel {
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_StreamKeepsToolNameAndBodyUnchang
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -107,6 +107,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_StreamKeepsToolNameAndBodyUnchang
|
||||
|
||||
// 2) only auth is replaced; inbound auth/cookie are not forwarded
|
||||
require.Equal(t, "Bearer oauth-token", upstream.lastReq.Header.Get("Authorization"))
|
||||
require.Equal(t, "codex_cli_rs/0.1.0", upstream.lastReq.Header.Get("User-Agent"))
|
||||
require.Empty(t, upstream.lastReq.Header.Get("Cookie"))
|
||||
require.Empty(t, upstream.lastReq.Header.Get("X-Api-Key"))
|
||||
require.Empty(t, upstream.lastReq.Header.Get("X-Goog-Api-Key"))
|
||||
@@ -154,7 +155,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_DisabledUsesLegacyTransform(t *te
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": false},
|
||||
Extra: map[string]any{"openai_passthrough": false},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -207,7 +208,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_ResponseHeadersAllowXCodex(t *tes
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -249,7 +250,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_UpstreamErrorIncludesPassthroughF
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -267,7 +268,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_UpstreamErrorIncludesPassthroughF
|
||||
require.True(t, arr[len(arr)-1].Passthrough)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayService_OAuthPassthrough_RequiresCodexUAOrForceFlag(t *testing.T) {
|
||||
func TestOpenAIGatewayService_OAuthPassthrough_NonCodexUAStillPassthroughWhenEnabled(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -297,7 +298,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_RequiresCodexUAOrForceFlag(t *tes
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -305,16 +306,8 @@ func TestOpenAIGatewayService_OAuthPassthrough_RequiresCodexUAOrForceFlag(t *tes
|
||||
|
||||
_, err := svc.Forward(context.Background(), c, account, inputBody)
|
||||
require.NoError(t, err)
|
||||
// not codex, not forced => legacy transform should run
|
||||
require.Contains(t, string(upstream.lastBody), `"store":false`)
|
||||
require.Contains(t, string(upstream.lastBody), `"stream":true`)
|
||||
|
||||
// now enable force flag => should passthrough and keep bytes
|
||||
upstream2 := &httpUpstreamRecorder{resp: resp}
|
||||
svc2 := &OpenAIGatewayService{cfg: &config.Config{Gateway: config.GatewayConfig{ForceCodexCLI: true}}, httpUpstream: upstream2}
|
||||
_, err = svc2.Forward(context.Background(), c, account, inputBody)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, inputBody, upstream2.lastBody)
|
||||
require.Equal(t, inputBody, upstream.lastBody)
|
||||
require.Equal(t, "curl/8.0", upstream.lastReq.Header.Get("User-Agent"))
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayService_OAuthPassthrough_StreamingSetsFirstTokenMs(t *testing.T) {
|
||||
@@ -352,7 +345,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_StreamingSetsFirstTokenMs(t *test
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -406,7 +399,7 @@ func TestOpenAIGatewayService_OAuthPassthrough_StreamClientDisconnectStillCollec
|
||||
Type: AccountTypeOAuth,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"access_token": "oauth-token", "chatgpt_account_id": "chatgpt-acc"},
|
||||
Extra: map[string]any{"openai_oauth_passthrough": true},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
@@ -421,3 +414,48 @@ func TestOpenAIGatewayService_OAuthPassthrough_StreamClientDisconnectStillCollec
|
||||
require.Equal(t, 7, result.Usage.OutputTokens)
|
||||
require.Equal(t, 3, result.Usage.CacheReadInputTokens)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayService_APIKeyPassthrough_PreservesBodyAndUsesResponsesEndpoint(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", bytes.NewReader(nil))
|
||||
c.Request.Header.Set("User-Agent", "curl/8.0")
|
||||
c.Request.Header.Set("X-Test", "keep")
|
||||
|
||||
originalBody := []byte(`{"model":"gpt-5.2","stream":false,"max_output_tokens":128,"input":[{"type":"text","text":"hi"}]}`)
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}, "x-request-id": []string{"rid"}},
|
||||
Body: io.NopCloser(strings.NewReader(`{"output":[],"usage":{"input_tokens":1,"output_tokens":1,"input_tokens_details":{"cached_tokens":0}}}`)),
|
||||
}
|
||||
upstream := &httpUpstreamRecorder{resp: resp}
|
||||
|
||||
svc := &OpenAIGatewayService{
|
||||
cfg: &config.Config{Gateway: config.GatewayConfig{ForceCodexCLI: false}},
|
||||
httpUpstream: upstream,
|
||||
}
|
||||
|
||||
account := &Account{
|
||||
ID: 456,
|
||||
Name: "apikey-acc",
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeAPIKey,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{"api_key": "sk-api-key", "base_url": "https://api.openai.com"},
|
||||
Extra: map[string]any{"openai_passthrough": true},
|
||||
Status: StatusActive,
|
||||
Schedulable: true,
|
||||
RateMultiplier: f64p(1),
|
||||
}
|
||||
|
||||
_, err := svc.Forward(context.Background(), c, account, originalBody)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, upstream.lastReq)
|
||||
require.Equal(t, originalBody, upstream.lastBody)
|
||||
require.Equal(t, "https://api.openai.com/v1/responses", upstream.lastReq.URL.String())
|
||||
require.Equal(t, "Bearer sk-api-key", upstream.lastReq.Header.Get("Authorization"))
|
||||
require.Equal(t, "curl/8.0", upstream.lastReq.Header.Get("User-Agent"))
|
||||
require.Equal(t, "keep", upstream.lastReq.Header.Get("X-Test"))
|
||||
}
|
||||
|
||||
10
backend/migrations/053_add_security_secrets.sql
Normal file
10
backend/migrations/053_add_security_secrets.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
-- 存储系统级密钥(如 JWT 签名密钥、TOTP 加密密钥)
|
||||
CREATE TABLE IF NOT EXISTS security_secrets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
key VARCHAR(100) NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_security_secrets_key ON security_secrets (key);
|
||||
@@ -866,77 +866,30 @@
|
||||
<div v-if="form.platform !== 'gemini'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
|
||||
<!-- Mode Toggle -->
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'whitelist'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'whitelist'
|
||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelWhitelist') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'mapping'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'mapping'
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelMapping') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
<div
|
||||
v-if="isOpenAIModelRestrictionDisabled"
|
||||
class="mb-3 rounded-lg bg-amber-50 p-3 dark:bg-amber-900/20"
|
||||
>
|
||||
<p class="text-xs text-amber-700 dark:text-amber-400">
|
||||
{{ t('admin.accounts.openai.modelRestrictionDisabledByPassthrough') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Mapping Mode -->
|
||||
<div v-else>
|
||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||
<template v-else>
|
||||
<!-- Mode Toggle -->
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'whitelist'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'whitelist'
|
||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1 inline h-4 w-4"
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -945,13 +898,70 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.mapRequestModels') }}
|
||||
{{ t('admin.accounts.modelWhitelist') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'mapping'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'mapping'
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelMapping') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Mapping Mode -->
|
||||
<div v-else>
|
||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||
<svg
|
||||
class="mr-1 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.mapRequestModels') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Model Mapping List -->
|
||||
<div v-if="modelMappings.length > 0" class="mb-3 space-y-2">
|
||||
<div
|
||||
@@ -1022,19 +1032,20 @@
|
||||
{{ t('admin.accounts.addMapping') }}
|
||||
</button>
|
||||
|
||||
<!-- Quick Add Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in presetMappings"
|
||||
:key="preset.label"
|
||||
type="button"
|
||||
@click="addPresetMapping(preset.from, preset.to)"
|
||||
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||
>
|
||||
+ {{ preset.label }}
|
||||
</button>
|
||||
<!-- Quick Add Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in presetMappings"
|
||||
:key="preset.label"
|
||||
type="button"
|
||||
@click="addPresetMapping(preset.from, preset.to)"
|
||||
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||
>
|
||||
+ {{ preset.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Custom Error Codes Section -->
|
||||
@@ -1562,6 +1573,36 @@
|
||||
<p class="input-hint">{{ t('admin.accounts.expiresAtHint') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI 自动透传开关(OAuth/API Key) -->
|
||||
<div
|
||||
v-if="form.platform === 'openai'"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.openai.oauthPassthrough') }}</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.oauthPassthroughDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="openaiPassthroughEnabled = !openaiPassthroughEnabled"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||
openaiPassthroughEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
openaiPassthroughEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -2143,6 +2184,7 @@ const selectedErrorCodes = ref<number[]>([])
|
||||
const customErrorCodeInput = ref<number | null>(null)
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const autoPauseOnExpired = ref(true)
|
||||
const openaiPassthroughEnabled = ref(false)
|
||||
const enableSoraOnOpenAIOAuth = ref(false) // OpenAI OAuth 时同时启用 Sora
|
||||
const mixedScheduling = ref(false) // For antigravity accounts: enable mixed scheduling
|
||||
const antigravityAccountType = ref<'oauth' | 'upstream'>('oauth') // For antigravity: oauth or upstream
|
||||
@@ -2192,6 +2234,10 @@ const geminiSelectedTier = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const isOpenAIModelRestrictionDisabled = computed(() =>
|
||||
form.platform === 'openai' && openaiPassthroughEnabled.value
|
||||
)
|
||||
|
||||
const geminiQuotaDocs = {
|
||||
codeAssist: 'https://developers.google.com/gemini-code-assist/resources/quotas',
|
||||
aiStudio: 'https://ai.google.dev/pricing',
|
||||
@@ -2362,6 +2408,9 @@ watch(
|
||||
if (newPlatform !== 'anthropic') {
|
||||
interceptWarmupRequests.value = false
|
||||
}
|
||||
if (newPlatform !== 'openai') {
|
||||
openaiPassthroughEnabled.value = false
|
||||
}
|
||||
// Reset OAuth states
|
||||
oauth.resetState()
|
||||
openaiOAuth.resetState()
|
||||
@@ -2615,6 +2664,7 @@ const resetForm = () => {
|
||||
customErrorCodeInput.value = null
|
||||
interceptWarmupRequests.value = false
|
||||
autoPauseOnExpired.value = true
|
||||
openaiPassthroughEnabled.value = false
|
||||
enableSoraOnOpenAIOAuth.value = false
|
||||
// Reset quota control state
|
||||
windowCostEnabled.value = false
|
||||
@@ -2645,6 +2695,21 @@ const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const buildOpenAIPassthroughExtra = (base?: Record<string, unknown>): Record<string, unknown> | undefined => {
|
||||
if (form.platform !== 'openai') {
|
||||
return base
|
||||
}
|
||||
|
||||
const extra: Record<string, unknown> = { ...(base || {}) }
|
||||
if (openaiPassthroughEnabled.value) {
|
||||
extra.openai_passthrough = true
|
||||
} else {
|
||||
delete extra.openai_passthrough
|
||||
delete extra.openai_oauth_passthrough
|
||||
}
|
||||
return Object.keys(extra).length > 0 ? extra : undefined
|
||||
}
|
||||
|
||||
// Helper function to create account with mixed channel warning handling
|
||||
const doCreateAccount = async (payload: any) => {
|
||||
submitting.value = true
|
||||
@@ -2775,10 +2840,12 @@ const handleSubmit = async () => {
|
||||
credentials.tier_id = geminiTierAIStudio.value
|
||||
}
|
||||
|
||||
// Add model mapping if configured
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
if (modelMapping) {
|
||||
credentials.model_mapping = modelMapping
|
||||
// Add model mapping if configured(OpenAI 开启自动透传时不应用)
|
||||
if (!isOpenAIModelRestrictionDisabled.value) {
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
if (modelMapping) {
|
||||
credentials.model_mapping = modelMapping
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom error codes if enabled
|
||||
@@ -2796,10 +2863,12 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
form.credentials = credentials
|
||||
const extra = buildOpenAIPassthroughExtra()
|
||||
|
||||
await doCreateAccount({
|
||||
...form,
|
||||
group_ids: form.group_ids,
|
||||
extra,
|
||||
auto_pause_on_expired: autoPauseOnExpired.value
|
||||
})
|
||||
}
|
||||
@@ -2879,7 +2948,8 @@ const handleOpenAIExchange = async (authCode: string) => {
|
||||
if (!tokenInfo) return
|
||||
|
||||
const credentials = openaiOAuth.buildCredentials(tokenInfo)
|
||||
const extra = openaiOAuth.buildExtraInfo(tokenInfo)
|
||||
const oauthExtra = openaiOAuth.buildExtraInfo(tokenInfo) as Record<string, unknown> | undefined
|
||||
const extra = buildOpenAIPassthroughExtra(oauthExtra)
|
||||
|
||||
// 应用临时不可调度配置
|
||||
if (!applyTempUnschedConfig(credentials)) {
|
||||
@@ -2916,10 +2986,12 @@ const handleOpenAIExchange = async (authCode: string) => {
|
||||
}
|
||||
|
||||
// 建立关联关系
|
||||
const soraExtra = {
|
||||
...extra,
|
||||
const soraExtra: Record<string, unknown> = {
|
||||
...(extra || {}),
|
||||
linked_openai_account_id: String(openaiAccount.id)
|
||||
}
|
||||
delete soraExtra.openai_passthrough
|
||||
delete soraExtra.openai_oauth_passthrough
|
||||
|
||||
await adminAPI.accounts.create({
|
||||
name: `${form.name} (Sora)`,
|
||||
@@ -2991,7 +3063,8 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
|
||||
}
|
||||
|
||||
const credentials = openaiOAuth.buildCredentials(tokenInfo)
|
||||
const extra = openaiOAuth.buildExtraInfo(tokenInfo)
|
||||
const oauthExtra = openaiOAuth.buildExtraInfo(tokenInfo) as Record<string, unknown> | undefined
|
||||
const extra = buildOpenAIPassthroughExtra(oauthExtra)
|
||||
|
||||
// Generate account name with index for batch
|
||||
const accountName = refreshTokens.length > 1 ? `${form.name} #${i + 1}` : form.name
|
||||
|
||||
@@ -69,77 +69,30 @@
|
||||
<div v-if="account.platform !== 'gemini' && account.platform !== 'antigravity'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
|
||||
<!-- Mode Toggle -->
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'whitelist'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'whitelist'
|
||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelWhitelist') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'mapping'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'mapping'
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelMapping') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="account?.platform || 'anthropic'" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
<div
|
||||
v-if="isOpenAIModelRestrictionDisabled"
|
||||
class="mb-3 rounded-lg bg-amber-50 p-3 dark:bg-amber-900/20"
|
||||
>
|
||||
<p class="text-xs text-amber-700 dark:text-amber-400">
|
||||
{{ t('admin.accounts.openai.modelRestrictionDisabledByPassthrough') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Mapping Mode -->
|
||||
<div v-else>
|
||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||
<template v-else>
|
||||
<!-- Mode Toggle -->
|
||||
<div class="mb-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'whitelist'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'whitelist'
|
||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1 inline h-4 w-4"
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
@@ -148,13 +101,70 @@
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.mapRequestModels') }}
|
||||
{{ t('admin.accounts.modelWhitelist') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="modelRestrictionMode = 'mapping'"
|
||||
:class="[
|
||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||
modelRestrictionMode === 'mapping'
|
||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||
]"
|
||||
>
|
||||
<svg
|
||||
class="mr-1.5 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.modelMapping') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="account?.platform || 'anthropic'" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
t('admin.accounts.supportsAllModels')
|
||||
}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Mapping Mode -->
|
||||
<div v-else>
|
||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||
<svg
|
||||
class="mr-1 inline h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{{ t('admin.accounts.mapRequestModels') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Model Mapping List -->
|
||||
<div v-if="modelMappings.length > 0" class="mb-3 space-y-2">
|
||||
<div
|
||||
@@ -225,19 +235,20 @@
|
||||
{{ t('admin.accounts.addMapping') }}
|
||||
</button>
|
||||
|
||||
<!-- Quick Add Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in presetMappings"
|
||||
:key="preset.label"
|
||||
type="button"
|
||||
@click="addPresetMapping(preset.from, preset.to)"
|
||||
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||
>
|
||||
+ {{ preset.label }}
|
||||
</button>
|
||||
<!-- Quick Add Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in presetMappings"
|
||||
:key="preset.label"
|
||||
type="button"
|
||||
@click="addPresetMapping(preset.from, preset.to)"
|
||||
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||
>
|
||||
+ {{ preset.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Custom Error Codes Section -->
|
||||
@@ -694,9 +705,9 @@
|
||||
<p class="input-hint">{{ t('admin.accounts.expiresAtHint') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI OAuth passthrough toggle (OpenAI OAuth only) -->
|
||||
<!-- OpenAI 自动透传开关(OAuth/API Key) -->
|
||||
<div
|
||||
v-if="account?.platform === 'openai' && account?.type === 'oauth'"
|
||||
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'apikey')"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -708,16 +719,16 @@
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="openaiOAuthPassthroughEnabled = !openaiOAuthPassthroughEnabled"
|
||||
@click="openaiPassthroughEnabled = !openaiPassthroughEnabled"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||
openaiOAuthPassthroughEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
openaiPassthroughEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
openaiOAuthPassthroughEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
openaiPassthroughEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
@@ -1133,8 +1144,11 @@ const sessionIdleTimeout = ref<number | null>(null)
|
||||
const tlsFingerprintEnabled = ref(false)
|
||||
const sessionIdMaskingEnabled = ref(false)
|
||||
|
||||
// OpenAI OAuth: passthrough mode toggle
|
||||
const openaiOAuthPassthroughEnabled = ref(false)
|
||||
// OpenAI 自动透传开关(OAuth/API Key)
|
||||
const openaiPassthroughEnabled = ref(false)
|
||||
const isOpenAIModelRestrictionDisabled = computed(() =>
|
||||
props.account?.platform === 'openai' && openaiPassthroughEnabled.value
|
||||
)
|
||||
|
||||
// Computed: current preset mappings based on platform
|
||||
const presetMappings = computed(() => getPresetMappingsByPlatform(props.account?.platform || 'anthropic'))
|
||||
@@ -1223,10 +1237,10 @@ watch(
|
||||
const extra = newAccount.extra as Record<string, unknown> | undefined
|
||||
mixedScheduling.value = extra?.mixed_scheduling === true
|
||||
|
||||
// Load OpenAI OAuth passthrough toggle (OpenAI OAuth only)
|
||||
openaiOAuthPassthroughEnabled.value = false
|
||||
if (newAccount.platform === 'openai' && newAccount.type === 'oauth') {
|
||||
openaiOAuthPassthroughEnabled.value = extra?.openai_oauth_passthrough === true
|
||||
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
|
||||
openaiPassthroughEnabled.value = false
|
||||
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) {
|
||||
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
|
||||
}
|
||||
|
||||
// Load antigravity model mapping (Antigravity 只支持映射模式)
|
||||
@@ -1614,7 +1628,7 @@ const handleSubmit = async () => {
|
||||
if (props.account.type === 'apikey') {
|
||||
const currentCredentials = (props.account.credentials as Record<string, unknown>) || {}
|
||||
const newBaseUrl = editBaseUrl.value.trim() || defaultBaseUrl.value
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
const shouldApplyModelMapping = !(props.account.platform === 'openai' && openaiPassthroughEnabled.value)
|
||||
|
||||
// Always update credentials for apikey type to handle model mapping changes
|
||||
const newCredentials: Record<string, unknown> = {
|
||||
@@ -1634,9 +1648,14 @@ const handleSubmit = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Add model mapping if configured
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
// Add model mapping if configured(OpenAI 开启自动透传时保留现有映射,不再编辑)
|
||||
if (shouldApplyModelMapping) {
|
||||
const modelMapping = buildModelMappingObject(modelRestrictionMode.value, allowedModels.value, modelMappings.value)
|
||||
if (modelMapping) {
|
||||
newCredentials.model_mapping = modelMapping
|
||||
}
|
||||
} else if (currentCredentials.model_mapping) {
|
||||
newCredentials.model_mapping = currentCredentials.model_mapping
|
||||
}
|
||||
|
||||
// Add custom error codes if enabled
|
||||
@@ -1765,13 +1784,14 @@ const handleSubmit = async () => {
|
||||
updatePayload.extra = newExtra
|
||||
}
|
||||
|
||||
// For OpenAI OAuth accounts, handle passthrough mode in extra
|
||||
if (props.account.platform === 'openai' && props.account.type === 'oauth') {
|
||||
// For OpenAI OAuth/API Key accounts, handle passthrough mode in extra
|
||||
if (props.account.platform === 'openai' && (props.account.type === 'oauth' || props.account.type === 'apikey')) {
|
||||
const currentExtra = (props.account.extra as Record<string, unknown>) || {}
|
||||
const newExtra: Record<string, unknown> = { ...currentExtra }
|
||||
if (openaiOAuthPassthroughEnabled.value) {
|
||||
newExtra.openai_oauth_passthrough = true
|
||||
if (openaiPassthroughEnabled.value) {
|
||||
newExtra.openai_passthrough = true
|
||||
} else {
|
||||
delete newExtra.openai_passthrough
|
||||
delete newExtra.openai_oauth_passthrough
|
||||
}
|
||||
updatePayload.extra = newExtra
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
</template>
|
||||
|
||||
<template #cell-user_agent="{ row }">
|
||||
<span v-if="row.user_agent" class="text-sm text-gray-600 dark:text-gray-400 max-w-[150px] truncate block" :title="row.user_agent">{{ formatUserAgent(row.user_agent) }}</span>
|
||||
<span v-if="row.user_agent" class="text-sm text-gray-600 dark:text-gray-400 block max-w-[320px] whitespace-normal break-all" :title="row.user_agent">{{ formatUserAgent(row.user_agent) }}</span>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-gray-500">-</span>
|
||||
</template>
|
||||
|
||||
@@ -284,16 +284,7 @@ const formatCacheTokens = (tokens: number): string => {
|
||||
}
|
||||
|
||||
const formatUserAgent = (ua: string): string => {
|
||||
// 提取主要客户端标识
|
||||
if (ua.includes('claude-cli')) return ua.match(/claude-cli\/[\d.]+/)?.[0] || 'Claude CLI'
|
||||
if (ua.includes('Cursor')) return 'Cursor'
|
||||
if (ua.includes('VSCode') || ua.includes('vscode')) return 'VS Code'
|
||||
if (ua.includes('Continue')) return 'Continue'
|
||||
if (ua.includes('Cline')) return 'Cline'
|
||||
if (ua.includes('OpenAI')) return 'OpenAI SDK'
|
||||
if (ua.includes('anthropic')) return 'Anthropic SDK'
|
||||
// 截断过长的 UA
|
||||
return ua.length > 30 ? ua.substring(0, 30) + '...' : ua
|
||||
return ua
|
||||
}
|
||||
|
||||
const formatDuration = (ms: number | null | undefined): string => {
|
||||
|
||||
@@ -1533,7 +1533,8 @@ export default {
|
||||
apiKeyHint: 'Your OpenAI API Key',
|
||||
oauthPassthrough: 'Auto passthrough (auth only)',
|
||||
oauthPassthroughDesc:
|
||||
'When enabled, applies to Codex CLI requests only: the gateway forwards request/response as-is and only swaps OAuth auth, while keeping billing/concurrency/audit. Disable to rollback if you hit 4xx or compatibility issues.',
|
||||
'When enabled, this OpenAI account uses automatic passthrough: the gateway forwards request/response as-is and only swaps auth, while keeping billing/concurrency/audit and necessary safety filtering.',
|
||||
modelRestrictionDisabledByPassthrough: 'Automatic passthrough is enabled: model whitelist/mapping will not take effect.',
|
||||
enableSora: 'Enable Sora simultaneously',
|
||||
enableSoraHint: 'Sora uses the same OpenAI account. Enable to create Sora account simultaneously.'
|
||||
},
|
||||
|
||||
@@ -1682,7 +1682,8 @@ export default {
|
||||
apiKeyHint: '您的 OpenAI API Key',
|
||||
oauthPassthrough: '自动透传(仅替换认证)',
|
||||
oauthPassthroughDesc:
|
||||
'开启后,仅对 Codex CLI 请求生效:网关将原样透传请求与响应内容,只替换 OAuth 认证并保留计费/并发/审计;如遇 4xx/兼容性问题可关闭回滚。',
|
||||
'开启后,该 OpenAI 账号将自动透传请求与响应,仅替换认证并保留计费/并发/审计及必要安全过滤;如遇兼容性问题可随时关闭回滚。',
|
||||
modelRestrictionDisabledByPassthrough: '已开启自动透传:模型白名单/映射不会生效。',
|
||||
enableSora: '同时启用 Sora',
|
||||
enableSoraHint: 'Sora 使用相同的 OpenAI 账号,开启后将同时创建 Sora 平台账号'
|
||||
},
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
</template>
|
||||
|
||||
<template #cell-user_agent="{ row }">
|
||||
<span v-if="row.user_agent" class="text-sm text-gray-600 dark:text-gray-400 max-w-[150px] truncate block" :title="row.user_agent">{{ formatUserAgent(row.user_agent) }}</span>
|
||||
<span v-if="row.user_agent" class="text-sm text-gray-600 dark:text-gray-400 block max-w-[320px] whitespace-normal break-all" :title="row.user_agent">{{ formatUserAgent(row.user_agent) }}</span>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-gray-500">-</span>
|
||||
</template>
|
||||
|
||||
@@ -545,16 +545,7 @@ const formatDuration = (ms: number): string => {
|
||||
}
|
||||
|
||||
const formatUserAgent = (ua: string): string => {
|
||||
// 提取主要客户端标识
|
||||
if (ua.includes('claude-cli')) return ua.match(/claude-cli\/[\d.]+/)?.[0] || 'Claude CLI'
|
||||
if (ua.includes('Cursor')) return 'Cursor'
|
||||
if (ua.includes('VSCode') || ua.includes('vscode')) return 'VS Code'
|
||||
if (ua.includes('Continue')) return 'Continue'
|
||||
if (ua.includes('Cline')) return 'Cline'
|
||||
if (ua.includes('OpenAI')) return 'OpenAI SDK'
|
||||
if (ua.includes('anthropic')) return 'Anthropic SDK'
|
||||
// 截断过长的 UA
|
||||
return ua.length > 30 ? ua.substring(0, 30) + '...' : ua
|
||||
return ua
|
||||
}
|
||||
|
||||
const formatTokens = (value: number): string => {
|
||||
|
||||
55
openai-passthrough-regression-checklist-20260212.md
Normal file
55
openai-passthrough-regression-checklist-20260212.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# OpenAI 自动透传回归测试清单(2026-02-12)
|
||||
|
||||
## 目标
|
||||
- 验证 OpenAI 账号(OAuth/API Key)“自动透传”开关在创建页与编辑页可正确开关。
|
||||
- 验证开启后请求透传(仅替换认证),并保留计费/并发/审计等网关能力。
|
||||
- 验证 `User-Agent` 头透传到上游,且 Usage 页面展示原始 UA(不映射、不截断)。
|
||||
|
||||
## 自动化测试
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
(cd backend && go test ./internal/service -run 'OpenAIGatewayService_.*Passthrough|TestAccount_IsOpenAIPassthroughEnabled|TestAccount_IsOpenAIOAuthPassthroughEnabled' -count=1)
|
||||
(cd backend && go test ./internal/handler -run OpenAI -count=1)
|
||||
pnpm --dir frontend run typecheck
|
||||
pnpm --dir frontend run lint:check
|
||||
```
|
||||
|
||||
预期:
|
||||
- 所有命令退出码为 `0`。
|
||||
|
||||
## 手工回归场景
|
||||
|
||||
### 场景1:创建 OpenAI API Key 账号并开启自动透传
|
||||
1. 进入管理端账号创建弹窗,平台选择 OpenAI,类型选择 API Key。
|
||||
2. 打开“自动透传(仅替换认证)”开关并保存。
|
||||
3. 检查创建后的账号详情。
|
||||
|
||||
预期:
|
||||
- `extra.openai_passthrough = true`。
|
||||
- 模型白名单/映射区域显示“不会生效”的提示。
|
||||
|
||||
### 场景2:编辑 OpenAI OAuth 账号开关可开可关
|
||||
1. 打开已有 OpenAI OAuth 账号编辑弹窗。
|
||||
2. 将“自动透传(仅替换认证)”从关切到开并保存。
|
||||
3. 再次进入编辑页,将开关从开切到关并保存。
|
||||
|
||||
预期:
|
||||
- 开启后:`extra.openai_passthrough = true`。
|
||||
- 关闭后:`extra.openai_passthrough` 与 `extra.openai_oauth_passthrough` 均被清理。
|
||||
|
||||
### 场景3:请求链路透传(含 User-Agent)
|
||||
1. 使用设置为“自动透传=开启”的 OpenAI 账号发起 `/v1/responses` 请求。
|
||||
2. 请求头设置 `User-Agent: codex_cli_rs/0.1.0`(或任意自定义 UA)。
|
||||
|
||||
预期:
|
||||
- 上游收到与下游一致的 `User-Agent`。
|
||||
- 请求体保持原样透传,仅认证头被替换为目标账号令牌。
|
||||
|
||||
### 场景4:Usage 页面原样显示 User-Agent
|
||||
1. 进入管理端用量表(Admin Usage)与用户侧用量页(User Usage)。
|
||||
2. 查找包含长 UA 的记录。
|
||||
|
||||
预期:
|
||||
- 显示原始 UA 文本(不再映射为 VS Code/Cursor 等)。
|
||||
- 文本可换行完整展示,不被 `...` 截断。
|
||||
Reference in New Issue
Block a user