Add composite group route registry

This commit is contained in:
Heatherm Huang
2026-07-01 09:27:47 +08:00
parent c8d1e2e16f
commit a008b63c16
55 changed files with 7867 additions and 57 deletions
+5 -3
View File
@@ -135,9 +135,11 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
channelRepository := repository.NewChannelRepository(db)
channelService := service.NewChannelService(channelRepository, groupRepository, apiKeyAuthCacheInvalidator, pricingService)
modelPricingResolver := service.NewModelPricingResolver(channelService, billingService)
compositeModelRouteRepository := repository.NewCompositeModelRouteRepository(client)
compositeRouteResolver := service.NewCompositeRouteResolver(compositeModelRouteRepository)
notificationEmailService := service.NewNotificationEmailService(settingRepository, emailService)
balanceNotifyService := service.ProvideBalanceNotifyService(emailService, settingRepository, accountRepository, notificationEmailService)
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService, channelService, modelPricingResolver, balanceNotifyService, serviceUserPlatformQuotaRepository)
gatewayService := service.NewGatewayService(accountRepository, groupRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, identityService, httpUpstream, deferredService, claudeTokenProvider, sessionLimitCache, rpmCache, digestSessionStore, settingService, tlsFingerprintProfileService, channelService, modelPricingResolver, compositeRouteResolver, balanceNotifyService, serviceUserPlatformQuotaRepository)
openAIOAuthClient := repository.NewOpenAIOAuthClient()
privacyClientFactory := providePrivacyClientFactory()
openAIOAuthService := service.ProvideOpenAIOAuthService(proxyRepository, openAIOAuthClient, privacyClientFactory)
@@ -180,7 +182,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
adminAccountRepository := repository.NewAdminAccountRepository(client, db, schedulerCache)
proxyExitInfoProber := repository.NewProxyExitInfoProber(configConfig)
proxyLatencyCache := repository.NewProxyLatencyCache(redisClient)
adminService := service.NewAdminService(userRepository, adminGroupRepository, adminAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, userRPMCache, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory, openAIGatewayService, affiliateService)
adminService := service.NewAdminService(userRepository, adminGroupRepository, adminAccountRepository, proxyRepository, apiKeyRepository, redeemCodeRepository, userGroupRateRepository, userRPMCache, billingCacheService, proxyExitInfoProber, proxyLatencyCache, apiKeyAuthCacheInvalidator, client, settingService, subscriptionService, userSubscriptionRepository, privacyClientFactory, openAIGatewayService, affiliateService, compositeModelRouteRepository, compositeRouteResolver)
adminUserHandler := admin.NewUserHandler(adminService, concurrencyService, serviceUserPlatformQuotaRepository, billingCache, totpService, userService, settingService)
groupCapacityService := service.NewGroupCapacityService(accountRepository, groupRepository, concurrencyService, sessionLimitCache, rpmCache)
groupHandler := admin.NewGroupHandler(adminService, dashboardService, groupCapacityService)
@@ -299,7 +301,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
auditLogMiddleware := middleware.NewAuditLogMiddleware(auditLogService)
stepUpAuthMiddleware := middleware.NewStepUpAuthMiddleware(totpService, userService, settingService)
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, auditLogMiddleware, stepUpAuthMiddleware, apiKeyService, subscriptionService, opsService, settingService, redisClient)
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, auditLogMiddleware, stepUpAuthMiddleware, apiKeyService, subscriptionService, opsService, settingService, compositeRouteResolver, redisClient)
httpServer := server.ProvideHTTPServer(configConfig, engine)
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
+175 -14
View File
@@ -29,6 +29,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
@@ -90,6 +91,8 @@ type Client struct {
ChannelMonitorHistory *ChannelMonitorHistoryClient
// ChannelMonitorRequestTemplate is the client for interacting with the ChannelMonitorRequestTemplate builders.
ChannelMonitorRequestTemplate *ChannelMonitorRequestTemplateClient
// CompositeModelRoute is the client for interacting with the CompositeModelRoute builders.
CompositeModelRoute *CompositeModelRouteClient
// ErrorPassthroughRule is the client for interacting with the ErrorPassthroughRule builders.
ErrorPassthroughRule *ErrorPassthroughRuleClient
// Group is the client for interacting with the Group builders.
@@ -163,6 +166,7 @@ func (c *Client) init() {
c.ChannelMonitorDailyRollup = NewChannelMonitorDailyRollupClient(c.config)
c.ChannelMonitorHistory = NewChannelMonitorHistoryClient(c.config)
c.ChannelMonitorRequestTemplate = NewChannelMonitorRequestTemplateClient(c.config)
c.CompositeModelRoute = NewCompositeModelRouteClient(c.config)
c.ErrorPassthroughRule = NewErrorPassthroughRuleClient(c.config)
c.Group = NewGroupClient(c.config)
c.IdempotencyRecord = NewIdempotencyRecordClient(c.config)
@@ -293,6 +297,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) {
ChannelMonitorDailyRollup: NewChannelMonitorDailyRollupClient(cfg),
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
ChannelMonitorRequestTemplate: NewChannelMonitorRequestTemplateClient(cfg),
CompositeModelRoute: NewCompositeModelRouteClient(cfg),
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
Group: NewGroupClient(cfg),
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
@@ -350,6 +355,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
ChannelMonitorDailyRollup: NewChannelMonitorDailyRollupClient(cfg),
ChannelMonitorHistory: NewChannelMonitorHistoryClient(cfg),
ChannelMonitorRequestTemplate: NewChannelMonitorRequestTemplateClient(cfg),
CompositeModelRoute: NewCompositeModelRouteClient(cfg),
ErrorPassthroughRule: NewErrorPassthroughRuleClient(cfg),
Group: NewGroupClient(cfg),
IdempotencyRecord: NewIdempotencyRecordClient(cfg),
@@ -407,7 +413,7 @@ func (c *Client) Use(hooks ...Hook) {
c.AuthIdentity, c.AuthIdentityChannel, c.BatchImageEvent, c.BatchImageItem,
c.BatchImageJob, c.ChannelMonitor, c.ChannelMonitorDailyRollup,
c.ChannelMonitorHistory, c.ChannelMonitorRequestTemplate,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.CompositeModelRoute, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
@@ -427,7 +433,7 @@ func (c *Client) Intercept(interceptors ...Interceptor) {
c.AuthIdentity, c.AuthIdentityChannel, c.BatchImageEvent, c.BatchImageItem,
c.BatchImageJob, c.ChannelMonitor, c.ChannelMonitorDailyRollup,
c.ChannelMonitorHistory, c.ChannelMonitorRequestTemplate,
c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.CompositeModelRoute, c.ErrorPassthroughRule, c.Group, c.IdempotencyRecord,
c.IdentityAdoptionDecision, c.PaymentAuditLog, c.PaymentOrder,
c.PaymentProviderInstance, c.PendingAuthSession, c.PromoCode, c.PromoCodeUsage,
c.Proxy, c.RedeemCode, c.SecuritySecret, c.Setting, c.SubscriptionPlan,
@@ -470,6 +476,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {
return c.ChannelMonitorHistory.mutate(ctx, m)
case *ChannelMonitorRequestTemplateMutation:
return c.ChannelMonitorRequestTemplate.mutate(ctx, m)
case *CompositeModelRouteMutation:
return c.CompositeModelRoute.mutate(ctx, m)
case *ErrorPassthroughRuleMutation:
return c.ErrorPassthroughRule.mutate(ctx, m)
case *GroupMutation:
@@ -2724,6 +2732,157 @@ func (c *ChannelMonitorRequestTemplateClient) mutate(ctx context.Context, m *Cha
}
}
// CompositeModelRouteClient is a client for the CompositeModelRoute schema.
type CompositeModelRouteClient struct {
config
}
// NewCompositeModelRouteClient returns a client for the CompositeModelRoute from the given config.
func NewCompositeModelRouteClient(c config) *CompositeModelRouteClient {
return &CompositeModelRouteClient{config: c}
}
// Use adds a list of mutation hooks to the hooks stack.
// A call to `Use(f, g, h)` equals to `compositemodelroute.Hooks(f(g(h())))`.
func (c *CompositeModelRouteClient) Use(hooks ...Hook) {
c.hooks.CompositeModelRoute = append(c.hooks.CompositeModelRoute, hooks...)
}
// Intercept adds a list of query interceptors to the interceptors stack.
// A call to `Intercept(f, g, h)` equals to `compositemodelroute.Intercept(f(g(h())))`.
func (c *CompositeModelRouteClient) Intercept(interceptors ...Interceptor) {
c.inters.CompositeModelRoute = append(c.inters.CompositeModelRoute, interceptors...)
}
// Create returns a builder for creating a CompositeModelRoute entity.
func (c *CompositeModelRouteClient) Create() *CompositeModelRouteCreate {
mutation := newCompositeModelRouteMutation(c.config, OpCreate)
return &CompositeModelRouteCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// CreateBulk returns a builder for creating a bulk of CompositeModelRoute entities.
func (c *CompositeModelRouteClient) CreateBulk(builders ...*CompositeModelRouteCreate) *CompositeModelRouteCreateBulk {
return &CompositeModelRouteCreateBulk{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 *CompositeModelRouteClient) MapCreateBulk(slice any, setFunc func(*CompositeModelRouteCreate, int)) *CompositeModelRouteCreateBulk {
rv := reflect.ValueOf(slice)
if rv.Kind() != reflect.Slice {
return &CompositeModelRouteCreateBulk{err: fmt.Errorf("calling to CompositeModelRouteClient.MapCreateBulk with wrong type %T, need slice", slice)}
}
builders := make([]*CompositeModelRouteCreate, rv.Len())
for i := 0; i < rv.Len(); i++ {
builders[i] = c.Create()
setFunc(builders[i], i)
}
return &CompositeModelRouteCreateBulk{config: c.config, builders: builders}
}
// Update returns an update builder for CompositeModelRoute.
func (c *CompositeModelRouteClient) Update() *CompositeModelRouteUpdate {
mutation := newCompositeModelRouteMutation(c.config, OpUpdate)
return &CompositeModelRouteUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOne returns an update builder for the given entity.
func (c *CompositeModelRouteClient) UpdateOne(_m *CompositeModelRoute) *CompositeModelRouteUpdateOne {
mutation := newCompositeModelRouteMutation(c.config, OpUpdateOne, withCompositeModelRoute(_m))
return &CompositeModelRouteUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// UpdateOneID returns an update builder for the given id.
func (c *CompositeModelRouteClient) UpdateOneID(id int64) *CompositeModelRouteUpdateOne {
mutation := newCompositeModelRouteMutation(c.config, OpUpdateOne, withCompositeModelRouteID(id))
return &CompositeModelRouteUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// Delete returns a delete builder for CompositeModelRoute.
func (c *CompositeModelRouteClient) Delete() *CompositeModelRouteDelete {
mutation := newCompositeModelRouteMutation(c.config, OpDelete)
return &CompositeModelRouteDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}
}
// DeleteOne returns a builder for deleting the given entity.
func (c *CompositeModelRouteClient) DeleteOne(_m *CompositeModelRoute) *CompositeModelRouteDeleteOne {
return c.DeleteOneID(_m.ID)
}
// DeleteOneID returns a builder for deleting the given entity by its id.
func (c *CompositeModelRouteClient) DeleteOneID(id int64) *CompositeModelRouteDeleteOne {
builder := c.Delete().Where(compositemodelroute.ID(id))
builder.mutation.id = &id
builder.mutation.op = OpDeleteOne
return &CompositeModelRouteDeleteOne{builder}
}
// Query returns a query builder for CompositeModelRoute.
func (c *CompositeModelRouteClient) Query() *CompositeModelRouteQuery {
return &CompositeModelRouteQuery{
config: c.config,
ctx: &QueryContext{Type: TypeCompositeModelRoute},
inters: c.Interceptors(),
}
}
// Get returns a CompositeModelRoute entity by its id.
func (c *CompositeModelRouteClient) Get(ctx context.Context, id int64) (*CompositeModelRoute, error) {
return c.Query().Where(compositemodelroute.ID(id)).Only(ctx)
}
// GetX is like Get, but panics if an error occurs.
func (c *CompositeModelRouteClient) GetX(ctx context.Context, id int64) *CompositeModelRoute {
obj, err := c.Get(ctx, id)
if err != nil {
panic(err)
}
return obj
}
// QueryGroup queries the group edge of a CompositeModelRoute.
func (c *CompositeModelRouteClient) QueryGroup(_m *CompositeModelRoute) *GroupQuery {
query := (&GroupClient{config: c.config}).Query()
query.path = func(context.Context) (fromV *sql.Selector, _ error) {
id := _m.ID
step := sqlgraph.NewStep(
sqlgraph.From(compositemodelroute.Table, compositemodelroute.FieldID, id),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, compositemodelroute.GroupTable, compositemodelroute.GroupColumn),
)
fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step)
return fromV, nil
}
return query
}
// Hooks returns the client hooks.
func (c *CompositeModelRouteClient) Hooks() []Hook {
hooks := c.hooks.CompositeModelRoute
return append(hooks[:len(hooks):len(hooks)], compositemodelroute.Hooks[:]...)
}
// Interceptors returns the client interceptors.
func (c *CompositeModelRouteClient) Interceptors() []Interceptor {
inters := c.inters.CompositeModelRoute
return append(inters[:len(inters):len(inters)], compositemodelroute.Interceptors[:]...)
}
func (c *CompositeModelRouteClient) mutate(ctx context.Context, m *CompositeModelRouteMutation) (Value, error) {
switch m.Op() {
case OpCreate:
return (&CompositeModelRouteCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdate:
return (&CompositeModelRouteUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpUpdateOne:
return (&CompositeModelRouteUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)
case OpDelete, OpDeleteOne:
return (&CompositeModelRouteDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)
default:
return nil, fmt.Errorf("ent: unknown CompositeModelRoute mutation op: %q", m.Op())
}
}
// ErrorPassthroughRuleClient is a client for the ErrorPassthroughRule schema.
type ErrorPassthroughRuleClient struct {
config
@@ -6669,23 +6828,25 @@ type (
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
AuthIdentityChannel, BatchImageEvent, BatchImageItem, BatchImageJob,
ChannelMonitor, ChannelMonitorDailyRollup, ChannelMonitorHistory,
ChannelMonitorRequestTemplate, ErrorPassthroughRule, Group, IdempotencyRecord,
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Hook
ChannelMonitorRequestTemplate, CompositeModelRoute,
ErrorPassthroughRule, Group, IdempotencyRecord, IdentityAdoptionDecision,
PaymentAuditLog, PaymentOrder, PaymentProviderInstance, PendingAuthSession,
PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting,
SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User,
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
UserPlatformQuota, UserSubscription []ent.Hook
}
inters struct {
APIKey, Account, AccountGroup, Announcement, AnnouncementRead, AuthIdentity,
AuthIdentityChannel, BatchImageEvent, BatchImageItem, BatchImageJob,
ChannelMonitor, ChannelMonitorDailyRollup, ChannelMonitorHistory,
ChannelMonitorRequestTemplate, ErrorPassthroughRule, Group, IdempotencyRecord,
IdentityAdoptionDecision, PaymentAuditLog, PaymentOrder,
PaymentProviderInstance, PendingAuthSession, PromoCode, PromoCodeUsage, Proxy,
RedeemCode, SecuritySecret, Setting, SubscriptionPlan, TLSFingerprintProfile,
UsageCleanupTask, UsageLog, User, UserAllowedGroup, UserAttributeDefinition,
UserAttributeValue, UserPlatformQuota, UserSubscription []ent.Interceptor
ChannelMonitorRequestTemplate, CompositeModelRoute,
ErrorPassthroughRule, Group, IdempotencyRecord, IdentityAdoptionDecision,
PaymentAuditLog, PaymentOrder, PaymentProviderInstance, PendingAuthSession,
PromoCode, PromoCodeUsage, Proxy, RedeemCode, SecuritySecret, Setting,
SubscriptionPlan, TLSFingerprintProfile, UsageCleanupTask, UsageLog, User,
UserAllowedGroup, UserAttributeDefinition, UserAttributeValue,
UserPlatformQuota, UserSubscription []ent.Interceptor
}
)
+264
View File
@@ -0,0 +1,264 @@
// 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/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/group"
)
// CompositeModelRoute is the model entity for the CompositeModelRoute schema.
type CompositeModelRoute 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"`
// DeletedAt holds the value of the "deleted_at" field.
DeletedAt *time.Time `json:"deleted_at,omitempty"`
// GroupID holds the value of the "group_id" field.
GroupID int64 `json:"group_id,omitempty"`
// Client-facing model identifier or prefix.
PublicModel string `json:"public_model,omitempty"`
// exact or prefix.
MatchType string `json:"match_type,omitempty"`
// Concrete provider platform.
TargetPlatform string `json:"target_platform,omitempty"`
// Provider model identifier; empty means public_model.
UpstreamModel string `json:"upstream_model,omitempty"`
// Endpoint scope such as any, messages, responses, chat_completions.
Endpoint string `json:"endpoint,omitempty"`
// Lower values win within the same match strength.
Priority int `json:"priority,omitempty"`
// Enabled holds the value of the "enabled" field.
Enabled bool `json:"enabled,omitempty"`
// Notes holds the value of the "notes" field.
Notes *string `json:"notes,omitempty"`
// Edges holds the relations/edges for other nodes in the graph.
// The values are being populated by the CompositeModelRouteQuery when eager-loading is set.
Edges CompositeModelRouteEdges `json:"edges"`
selectValues sql.SelectValues
}
// CompositeModelRouteEdges holds the relations/edges for other nodes in the graph.
type CompositeModelRouteEdges struct {
// Group holds the value of the group edge.
Group *Group `json:"group,omitempty"`
// loadedTypes holds the information for reporting if a
// type was loaded (or requested) in eager-loading or not.
loadedTypes [1]bool
}
// GroupOrErr returns the Group value or an error if the edge
// was not loaded in eager-loading, or loaded but was not found.
func (e CompositeModelRouteEdges) GroupOrErr() (*Group, error) {
if e.Group != nil {
return e.Group, nil
} else if e.loadedTypes[0] {
return nil, &NotFoundError{label: group.Label}
}
return nil, &NotLoadedError{edge: "group"}
}
// scanValues returns the types for scanning values from sql.Rows.
func (*CompositeModelRoute) scanValues(columns []string) ([]any, error) {
values := make([]any, len(columns))
for i := range columns {
switch columns[i] {
case compositemodelroute.FieldEnabled:
values[i] = new(sql.NullBool)
case compositemodelroute.FieldID, compositemodelroute.FieldGroupID, compositemodelroute.FieldPriority:
values[i] = new(sql.NullInt64)
case compositemodelroute.FieldPublicModel, compositemodelroute.FieldMatchType, compositemodelroute.FieldTargetPlatform, compositemodelroute.FieldUpstreamModel, compositemodelroute.FieldEndpoint, compositemodelroute.FieldNotes:
values[i] = new(sql.NullString)
case compositemodelroute.FieldCreatedAt, compositemodelroute.FieldUpdatedAt, compositemodelroute.FieldDeletedAt:
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 CompositeModelRoute fields.
func (_m *CompositeModelRoute) 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 compositemodelroute.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 compositemodelroute.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 compositemodelroute.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 compositemodelroute.FieldDeletedAt:
if value, ok := values[i].(*sql.NullTime); !ok {
return fmt.Errorf("unexpected type %T for field deleted_at", values[i])
} else if value.Valid {
_m.DeletedAt = new(time.Time)
*_m.DeletedAt = value.Time
}
case compositemodelroute.FieldGroupID:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field group_id", values[i])
} else if value.Valid {
_m.GroupID = value.Int64
}
case compositemodelroute.FieldPublicModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field public_model", values[i])
} else if value.Valid {
_m.PublicModel = value.String
}
case compositemodelroute.FieldMatchType:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field match_type", values[i])
} else if value.Valid {
_m.MatchType = value.String
}
case compositemodelroute.FieldTargetPlatform:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field target_platform", values[i])
} else if value.Valid {
_m.TargetPlatform = value.String
}
case compositemodelroute.FieldUpstreamModel:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field upstream_model", values[i])
} else if value.Valid {
_m.UpstreamModel = value.String
}
case compositemodelroute.FieldEndpoint:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field endpoint", values[i])
} else if value.Valid {
_m.Endpoint = value.String
}
case compositemodelroute.FieldPriority:
if value, ok := values[i].(*sql.NullInt64); !ok {
return fmt.Errorf("unexpected type %T for field priority", values[i])
} else if value.Valid {
_m.Priority = int(value.Int64)
}
case compositemodelroute.FieldEnabled:
if value, ok := values[i].(*sql.NullBool); !ok {
return fmt.Errorf("unexpected type %T for field enabled", values[i])
} else if value.Valid {
_m.Enabled = value.Bool
}
case compositemodelroute.FieldNotes:
if value, ok := values[i].(*sql.NullString); !ok {
return fmt.Errorf("unexpected type %T for field notes", values[i])
} else if value.Valid {
_m.Notes = new(string)
*_m.Notes = value.String
}
default:
_m.selectValues.Set(columns[i], values[i])
}
}
return nil
}
// Value returns the ent.Value that was dynamically selected and assigned to the CompositeModelRoute.
// This includes values selected through modifiers, order, etc.
func (_m *CompositeModelRoute) Value(name string) (ent.Value, error) {
return _m.selectValues.Get(name)
}
// QueryGroup queries the "group" edge of the CompositeModelRoute entity.
func (_m *CompositeModelRoute) QueryGroup() *GroupQuery {
return NewCompositeModelRouteClient(_m.config).QueryGroup(_m)
}
// Update returns a builder for updating this CompositeModelRoute.
// Note that you need to call CompositeModelRoute.Unwrap() before calling this method if this CompositeModelRoute
// was returned from a transaction, and the transaction was committed or rolled back.
func (_m *CompositeModelRoute) Update() *CompositeModelRouteUpdateOne {
return NewCompositeModelRouteClient(_m.config).UpdateOne(_m)
}
// Unwrap unwraps the CompositeModelRoute 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 *CompositeModelRoute) Unwrap() *CompositeModelRoute {
_tx, ok := _m.config.driver.(*txDriver)
if !ok {
panic("ent: CompositeModelRoute is not a transactional entity")
}
_m.config.driver = _tx.drv
return _m
}
// String implements the fmt.Stringer.
func (_m *CompositeModelRoute) String() string {
var builder strings.Builder
builder.WriteString("CompositeModelRoute(")
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(", ")
if v := _m.DeletedAt; v != nil {
builder.WriteString("deleted_at=")
builder.WriteString(v.Format(time.ANSIC))
}
builder.WriteString(", ")
builder.WriteString("group_id=")
builder.WriteString(fmt.Sprintf("%v", _m.GroupID))
builder.WriteString(", ")
builder.WriteString("public_model=")
builder.WriteString(_m.PublicModel)
builder.WriteString(", ")
builder.WriteString("match_type=")
builder.WriteString(_m.MatchType)
builder.WriteString(", ")
builder.WriteString("target_platform=")
builder.WriteString(_m.TargetPlatform)
builder.WriteString(", ")
builder.WriteString("upstream_model=")
builder.WriteString(_m.UpstreamModel)
builder.WriteString(", ")
builder.WriteString("endpoint=")
builder.WriteString(_m.Endpoint)
builder.WriteString(", ")
builder.WriteString("priority=")
builder.WriteString(fmt.Sprintf("%v", _m.Priority))
builder.WriteString(", ")
builder.WriteString("enabled=")
builder.WriteString(fmt.Sprintf("%v", _m.Enabled))
builder.WriteString(", ")
if v := _m.Notes; v != nil {
builder.WriteString("notes=")
builder.WriteString(*v)
}
builder.WriteByte(')')
return builder.String()
}
// CompositeModelRoutes is a parsable slice of CompositeModelRoute.
type CompositeModelRoutes []*CompositeModelRoute
@@ -0,0 +1,200 @@
// Code generated by ent, DO NOT EDIT.
package compositemodelroute
import (
"time"
"entgo.io/ent"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
)
const (
// Label holds the string label denoting the compositemodelroute type in the database.
Label = "composite_model_route"
// 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"
// FieldDeletedAt holds the string denoting the deleted_at field in the database.
FieldDeletedAt = "deleted_at"
// FieldGroupID holds the string denoting the group_id field in the database.
FieldGroupID = "group_id"
// FieldPublicModel holds the string denoting the public_model field in the database.
FieldPublicModel = "public_model"
// FieldMatchType holds the string denoting the match_type field in the database.
FieldMatchType = "match_type"
// FieldTargetPlatform holds the string denoting the target_platform field in the database.
FieldTargetPlatform = "target_platform"
// FieldUpstreamModel holds the string denoting the upstream_model field in the database.
FieldUpstreamModel = "upstream_model"
// FieldEndpoint holds the string denoting the endpoint field in the database.
FieldEndpoint = "endpoint"
// FieldPriority holds the string denoting the priority field in the database.
FieldPriority = "priority"
// FieldEnabled holds the string denoting the enabled field in the database.
FieldEnabled = "enabled"
// FieldNotes holds the string denoting the notes field in the database.
FieldNotes = "notes"
// EdgeGroup holds the string denoting the group edge name in mutations.
EdgeGroup = "group"
// Table holds the table name of the compositemodelroute in the database.
Table = "composite_model_routes"
// GroupTable is the table that holds the group relation/edge.
GroupTable = "composite_model_routes"
// GroupInverseTable is the table name for the Group entity.
// It exists in this package in order to avoid circular dependency with the "group" package.
GroupInverseTable = "groups"
// GroupColumn is the table column denoting the group relation/edge.
GroupColumn = "group_id"
)
// Columns holds all SQL columns for compositemodelroute fields.
var Columns = []string{
FieldID,
FieldCreatedAt,
FieldUpdatedAt,
FieldDeletedAt,
FieldGroupID,
FieldPublicModel,
FieldMatchType,
FieldTargetPlatform,
FieldUpstreamModel,
FieldEndpoint,
FieldPriority,
FieldEnabled,
FieldNotes,
}
// 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
}
// Note that the variables below are initialized by the runtime
// package on the initialization of the application. Therefore,
// it should be imported in the main as follows:
//
// import _ "github.com/Wei-Shaw/sub2api/ent/runtime"
var (
Hooks [1]ent.Hook
Interceptors [1]ent.Interceptor
// 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
// PublicModelValidator is a validator for the "public_model" field. It is called by the builders before save.
PublicModelValidator func(string) error
// DefaultMatchType holds the default value on creation for the "match_type" field.
DefaultMatchType string
// MatchTypeValidator is a validator for the "match_type" field. It is called by the builders before save.
MatchTypeValidator func(string) error
// DefaultTargetPlatform holds the default value on creation for the "target_platform" field.
DefaultTargetPlatform string
// TargetPlatformValidator is a validator for the "target_platform" field. It is called by the builders before save.
TargetPlatformValidator func(string) error
// DefaultUpstreamModel holds the default value on creation for the "upstream_model" field.
DefaultUpstreamModel string
// UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
UpstreamModelValidator func(string) error
// DefaultEndpoint holds the default value on creation for the "endpoint" field.
DefaultEndpoint string
// EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
EndpointValidator func(string) error
// DefaultPriority holds the default value on creation for the "priority" field.
DefaultPriority int
// DefaultEnabled holds the default value on creation for the "enabled" field.
DefaultEnabled bool
)
// OrderOption defines the ordering options for the CompositeModelRoute 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()
}
// ByDeletedAt orders the results by the deleted_at field.
func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldDeletedAt, opts...).ToFunc()
}
// ByGroupID orders the results by the group_id field.
func ByGroupID(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldGroupID, opts...).ToFunc()
}
// ByPublicModel orders the results by the public_model field.
func ByPublicModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPublicModel, opts...).ToFunc()
}
// ByMatchType orders the results by the match_type field.
func ByMatchType(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldMatchType, opts...).ToFunc()
}
// ByTargetPlatform orders the results by the target_platform field.
func ByTargetPlatform(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldTargetPlatform, opts...).ToFunc()
}
// ByUpstreamModel orders the results by the upstream_model field.
func ByUpstreamModel(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldUpstreamModel, opts...).ToFunc()
}
// ByEndpoint orders the results by the endpoint field.
func ByEndpoint(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEndpoint, opts...).ToFunc()
}
// ByPriority orders the results by the priority field.
func ByPriority(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldPriority, opts...).ToFunc()
}
// ByEnabled orders the results by the enabled field.
func ByEnabled(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldEnabled, opts...).ToFunc()
}
// ByNotes orders the results by the notes field.
func ByNotes(opts ...sql.OrderTermOption) OrderOption {
return sql.OrderByField(FieldNotes, opts...).ToFunc()
}
// ByGroupField orders the results by group field.
func ByGroupField(field string, opts ...sql.OrderTermOption) OrderOption {
return func(s *sql.Selector) {
sqlgraph.OrderByNeighborTerms(s, newGroupStep(), sql.OrderByField(field, opts...))
}
}
func newGroupStep() *sqlgraph.Step {
return sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.To(GroupInverseTable, FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, GroupTable, GroupColumn),
)
}
+754
View File
@@ -0,0 +1,754 @@
// Code generated by ent, DO NOT EDIT.
package compositemodelroute
import (
"time"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// ID filters vertices based on their ID field.
func ID(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldID, id))
}
// IDEQ applies the EQ predicate on the ID field.
func IDEQ(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldID, id))
}
// IDNEQ applies the NEQ predicate on the ID field.
func IDNEQ(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldID, id))
}
// IDIn applies the In predicate on the ID field.
func IDIn(ids ...int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldID, ids...))
}
// IDNotIn applies the NotIn predicate on the ID field.
func IDNotIn(ids ...int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldID, ids...))
}
// IDGT applies the GT predicate on the ID field.
func IDGT(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldID, id))
}
// IDGTE applies the GTE predicate on the ID field.
func IDGTE(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldID, id))
}
// IDLT applies the LT predicate on the ID field.
func IDLT(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldID, id))
}
// IDLTE applies the LTE predicate on the ID field.
func IDLTE(id int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(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.CompositeModelRoute {
return predicate.CompositeModelRoute(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.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldUpdatedAt, v))
}
// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ.
func DeletedAt(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldDeletedAt, v))
}
// GroupID applies equality check predicate on the "group_id" field. It's identical to GroupIDEQ.
func GroupID(v int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldGroupID, v))
}
// PublicModel applies equality check predicate on the "public_model" field. It's identical to PublicModelEQ.
func PublicModel(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldPublicModel, v))
}
// MatchType applies equality check predicate on the "match_type" field. It's identical to MatchTypeEQ.
func MatchType(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldMatchType, v))
}
// TargetPlatform applies equality check predicate on the "target_platform" field. It's identical to TargetPlatformEQ.
func TargetPlatform(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldTargetPlatform, v))
}
// UpstreamModel applies equality check predicate on the "upstream_model" field. It's identical to UpstreamModelEQ.
func UpstreamModel(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldUpstreamModel, v))
}
// Endpoint applies equality check predicate on the "endpoint" field. It's identical to EndpointEQ.
func Endpoint(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldEndpoint, v))
}
// Priority applies equality check predicate on the "priority" field. It's identical to PriorityEQ.
func Priority(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldPriority, v))
}
// Enabled applies equality check predicate on the "enabled" field. It's identical to EnabledEQ.
func Enabled(v bool) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldEnabled, v))
}
// Notes applies equality check predicate on the "notes" field. It's identical to NotesEQ.
func Notes(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldNotes, v))
}
// CreatedAtEQ applies the EQ predicate on the "created_at" field.
func CreatedAtEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldCreatedAt, v))
}
// CreatedAtNEQ applies the NEQ predicate on the "created_at" field.
func CreatedAtNEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldCreatedAt, v))
}
// CreatedAtIn applies the In predicate on the "created_at" field.
func CreatedAtIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldCreatedAt, vs...))
}
// CreatedAtNotIn applies the NotIn predicate on the "created_at" field.
func CreatedAtNotIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldCreatedAt, vs...))
}
// CreatedAtGT applies the GT predicate on the "created_at" field.
func CreatedAtGT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldCreatedAt, v))
}
// CreatedAtGTE applies the GTE predicate on the "created_at" field.
func CreatedAtGTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldCreatedAt, v))
}
// CreatedAtLT applies the LT predicate on the "created_at" field.
func CreatedAtLT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldCreatedAt, v))
}
// CreatedAtLTE applies the LTE predicate on the "created_at" field.
func CreatedAtLTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldCreatedAt, v))
}
// UpdatedAtEQ applies the EQ predicate on the "updated_at" field.
func UpdatedAtEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldUpdatedAt, v))
}
// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field.
func UpdatedAtNEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldUpdatedAt, v))
}
// UpdatedAtIn applies the In predicate on the "updated_at" field.
func UpdatedAtIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldUpdatedAt, vs...))
}
// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field.
func UpdatedAtNotIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldUpdatedAt, vs...))
}
// UpdatedAtGT applies the GT predicate on the "updated_at" field.
func UpdatedAtGT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldUpdatedAt, v))
}
// UpdatedAtGTE applies the GTE predicate on the "updated_at" field.
func UpdatedAtGTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldUpdatedAt, v))
}
// UpdatedAtLT applies the LT predicate on the "updated_at" field.
func UpdatedAtLT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldUpdatedAt, v))
}
// UpdatedAtLTE applies the LTE predicate on the "updated_at" field.
func UpdatedAtLTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldUpdatedAt, v))
}
// DeletedAtEQ applies the EQ predicate on the "deleted_at" field.
func DeletedAtEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldDeletedAt, v))
}
// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field.
func DeletedAtNEQ(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldDeletedAt, v))
}
// DeletedAtIn applies the In predicate on the "deleted_at" field.
func DeletedAtIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldDeletedAt, vs...))
}
// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field.
func DeletedAtNotIn(vs ...time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldDeletedAt, vs...))
}
// DeletedAtGT applies the GT predicate on the "deleted_at" field.
func DeletedAtGT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldDeletedAt, v))
}
// DeletedAtGTE applies the GTE predicate on the "deleted_at" field.
func DeletedAtGTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldDeletedAt, v))
}
// DeletedAtLT applies the LT predicate on the "deleted_at" field.
func DeletedAtLT(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldDeletedAt, v))
}
// DeletedAtLTE applies the LTE predicate on the "deleted_at" field.
func DeletedAtLTE(v time.Time) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldDeletedAt, v))
}
// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field.
func DeletedAtIsNil() predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIsNull(FieldDeletedAt))
}
// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field.
func DeletedAtNotNil() predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotNull(FieldDeletedAt))
}
// GroupIDEQ applies the EQ predicate on the "group_id" field.
func GroupIDEQ(v int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldGroupID, v))
}
// GroupIDNEQ applies the NEQ predicate on the "group_id" field.
func GroupIDNEQ(v int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldGroupID, v))
}
// GroupIDIn applies the In predicate on the "group_id" field.
func GroupIDIn(vs ...int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldGroupID, vs...))
}
// GroupIDNotIn applies the NotIn predicate on the "group_id" field.
func GroupIDNotIn(vs ...int64) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldGroupID, vs...))
}
// PublicModelEQ applies the EQ predicate on the "public_model" field.
func PublicModelEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldPublicModel, v))
}
// PublicModelNEQ applies the NEQ predicate on the "public_model" field.
func PublicModelNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldPublicModel, v))
}
// PublicModelIn applies the In predicate on the "public_model" field.
func PublicModelIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldPublicModel, vs...))
}
// PublicModelNotIn applies the NotIn predicate on the "public_model" field.
func PublicModelNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldPublicModel, vs...))
}
// PublicModelGT applies the GT predicate on the "public_model" field.
func PublicModelGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldPublicModel, v))
}
// PublicModelGTE applies the GTE predicate on the "public_model" field.
func PublicModelGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldPublicModel, v))
}
// PublicModelLT applies the LT predicate on the "public_model" field.
func PublicModelLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldPublicModel, v))
}
// PublicModelLTE applies the LTE predicate on the "public_model" field.
func PublicModelLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldPublicModel, v))
}
// PublicModelContains applies the Contains predicate on the "public_model" field.
func PublicModelContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldPublicModel, v))
}
// PublicModelHasPrefix applies the HasPrefix predicate on the "public_model" field.
func PublicModelHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldPublicModel, v))
}
// PublicModelHasSuffix applies the HasSuffix predicate on the "public_model" field.
func PublicModelHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldPublicModel, v))
}
// PublicModelEqualFold applies the EqualFold predicate on the "public_model" field.
func PublicModelEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldPublicModel, v))
}
// PublicModelContainsFold applies the ContainsFold predicate on the "public_model" field.
func PublicModelContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldPublicModel, v))
}
// MatchTypeEQ applies the EQ predicate on the "match_type" field.
func MatchTypeEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldMatchType, v))
}
// MatchTypeNEQ applies the NEQ predicate on the "match_type" field.
func MatchTypeNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldMatchType, v))
}
// MatchTypeIn applies the In predicate on the "match_type" field.
func MatchTypeIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldMatchType, vs...))
}
// MatchTypeNotIn applies the NotIn predicate on the "match_type" field.
func MatchTypeNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldMatchType, vs...))
}
// MatchTypeGT applies the GT predicate on the "match_type" field.
func MatchTypeGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldMatchType, v))
}
// MatchTypeGTE applies the GTE predicate on the "match_type" field.
func MatchTypeGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldMatchType, v))
}
// MatchTypeLT applies the LT predicate on the "match_type" field.
func MatchTypeLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldMatchType, v))
}
// MatchTypeLTE applies the LTE predicate on the "match_type" field.
func MatchTypeLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldMatchType, v))
}
// MatchTypeContains applies the Contains predicate on the "match_type" field.
func MatchTypeContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldMatchType, v))
}
// MatchTypeHasPrefix applies the HasPrefix predicate on the "match_type" field.
func MatchTypeHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldMatchType, v))
}
// MatchTypeHasSuffix applies the HasSuffix predicate on the "match_type" field.
func MatchTypeHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldMatchType, v))
}
// MatchTypeEqualFold applies the EqualFold predicate on the "match_type" field.
func MatchTypeEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldMatchType, v))
}
// MatchTypeContainsFold applies the ContainsFold predicate on the "match_type" field.
func MatchTypeContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldMatchType, v))
}
// TargetPlatformEQ applies the EQ predicate on the "target_platform" field.
func TargetPlatformEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldTargetPlatform, v))
}
// TargetPlatformNEQ applies the NEQ predicate on the "target_platform" field.
func TargetPlatformNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldTargetPlatform, v))
}
// TargetPlatformIn applies the In predicate on the "target_platform" field.
func TargetPlatformIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldTargetPlatform, vs...))
}
// TargetPlatformNotIn applies the NotIn predicate on the "target_platform" field.
func TargetPlatformNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldTargetPlatform, vs...))
}
// TargetPlatformGT applies the GT predicate on the "target_platform" field.
func TargetPlatformGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldTargetPlatform, v))
}
// TargetPlatformGTE applies the GTE predicate on the "target_platform" field.
func TargetPlatformGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldTargetPlatform, v))
}
// TargetPlatformLT applies the LT predicate on the "target_platform" field.
func TargetPlatformLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldTargetPlatform, v))
}
// TargetPlatformLTE applies the LTE predicate on the "target_platform" field.
func TargetPlatformLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldTargetPlatform, v))
}
// TargetPlatformContains applies the Contains predicate on the "target_platform" field.
func TargetPlatformContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldTargetPlatform, v))
}
// TargetPlatformHasPrefix applies the HasPrefix predicate on the "target_platform" field.
func TargetPlatformHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldTargetPlatform, v))
}
// TargetPlatformHasSuffix applies the HasSuffix predicate on the "target_platform" field.
func TargetPlatformHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldTargetPlatform, v))
}
// TargetPlatformEqualFold applies the EqualFold predicate on the "target_platform" field.
func TargetPlatformEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldTargetPlatform, v))
}
// TargetPlatformContainsFold applies the ContainsFold predicate on the "target_platform" field.
func TargetPlatformContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldTargetPlatform, v))
}
// UpstreamModelEQ applies the EQ predicate on the "upstream_model" field.
func UpstreamModelEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldUpstreamModel, v))
}
// UpstreamModelNEQ applies the NEQ predicate on the "upstream_model" field.
func UpstreamModelNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldUpstreamModel, v))
}
// UpstreamModelIn applies the In predicate on the "upstream_model" field.
func UpstreamModelIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldUpstreamModel, vs...))
}
// UpstreamModelNotIn applies the NotIn predicate on the "upstream_model" field.
func UpstreamModelNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldUpstreamModel, vs...))
}
// UpstreamModelGT applies the GT predicate on the "upstream_model" field.
func UpstreamModelGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldUpstreamModel, v))
}
// UpstreamModelGTE applies the GTE predicate on the "upstream_model" field.
func UpstreamModelGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldUpstreamModel, v))
}
// UpstreamModelLT applies the LT predicate on the "upstream_model" field.
func UpstreamModelLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldUpstreamModel, v))
}
// UpstreamModelLTE applies the LTE predicate on the "upstream_model" field.
func UpstreamModelLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldUpstreamModel, v))
}
// UpstreamModelContains applies the Contains predicate on the "upstream_model" field.
func UpstreamModelContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldUpstreamModel, v))
}
// UpstreamModelHasPrefix applies the HasPrefix predicate on the "upstream_model" field.
func UpstreamModelHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldUpstreamModel, v))
}
// UpstreamModelHasSuffix applies the HasSuffix predicate on the "upstream_model" field.
func UpstreamModelHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldUpstreamModel, v))
}
// UpstreamModelEqualFold applies the EqualFold predicate on the "upstream_model" field.
func UpstreamModelEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldUpstreamModel, v))
}
// UpstreamModelContainsFold applies the ContainsFold predicate on the "upstream_model" field.
func UpstreamModelContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldUpstreamModel, v))
}
// EndpointEQ applies the EQ predicate on the "endpoint" field.
func EndpointEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldEndpoint, v))
}
// EndpointNEQ applies the NEQ predicate on the "endpoint" field.
func EndpointNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldEndpoint, v))
}
// EndpointIn applies the In predicate on the "endpoint" field.
func EndpointIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldEndpoint, vs...))
}
// EndpointNotIn applies the NotIn predicate on the "endpoint" field.
func EndpointNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldEndpoint, vs...))
}
// EndpointGT applies the GT predicate on the "endpoint" field.
func EndpointGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldEndpoint, v))
}
// EndpointGTE applies the GTE predicate on the "endpoint" field.
func EndpointGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldEndpoint, v))
}
// EndpointLT applies the LT predicate on the "endpoint" field.
func EndpointLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldEndpoint, v))
}
// EndpointLTE applies the LTE predicate on the "endpoint" field.
func EndpointLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldEndpoint, v))
}
// EndpointContains applies the Contains predicate on the "endpoint" field.
func EndpointContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldEndpoint, v))
}
// EndpointHasPrefix applies the HasPrefix predicate on the "endpoint" field.
func EndpointHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldEndpoint, v))
}
// EndpointHasSuffix applies the HasSuffix predicate on the "endpoint" field.
func EndpointHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldEndpoint, v))
}
// EndpointEqualFold applies the EqualFold predicate on the "endpoint" field.
func EndpointEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldEndpoint, v))
}
// EndpointContainsFold applies the ContainsFold predicate on the "endpoint" field.
func EndpointContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldEndpoint, v))
}
// PriorityEQ applies the EQ predicate on the "priority" field.
func PriorityEQ(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldPriority, v))
}
// PriorityNEQ applies the NEQ predicate on the "priority" field.
func PriorityNEQ(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldPriority, v))
}
// PriorityIn applies the In predicate on the "priority" field.
func PriorityIn(vs ...int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldPriority, vs...))
}
// PriorityNotIn applies the NotIn predicate on the "priority" field.
func PriorityNotIn(vs ...int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldPriority, vs...))
}
// PriorityGT applies the GT predicate on the "priority" field.
func PriorityGT(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldPriority, v))
}
// PriorityGTE applies the GTE predicate on the "priority" field.
func PriorityGTE(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldPriority, v))
}
// PriorityLT applies the LT predicate on the "priority" field.
func PriorityLT(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldPriority, v))
}
// PriorityLTE applies the LTE predicate on the "priority" field.
func PriorityLTE(v int) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldPriority, v))
}
// EnabledEQ applies the EQ predicate on the "enabled" field.
func EnabledEQ(v bool) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldEnabled, v))
}
// EnabledNEQ applies the NEQ predicate on the "enabled" field.
func EnabledNEQ(v bool) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldEnabled, v))
}
// NotesEQ applies the EQ predicate on the "notes" field.
func NotesEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEQ(FieldNotes, v))
}
// NotesNEQ applies the NEQ predicate on the "notes" field.
func NotesNEQ(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNEQ(FieldNotes, v))
}
// NotesIn applies the In predicate on the "notes" field.
func NotesIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIn(FieldNotes, vs...))
}
// NotesNotIn applies the NotIn predicate on the "notes" field.
func NotesNotIn(vs ...string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotIn(FieldNotes, vs...))
}
// NotesGT applies the GT predicate on the "notes" field.
func NotesGT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGT(FieldNotes, v))
}
// NotesGTE applies the GTE predicate on the "notes" field.
func NotesGTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldGTE(FieldNotes, v))
}
// NotesLT applies the LT predicate on the "notes" field.
func NotesLT(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLT(FieldNotes, v))
}
// NotesLTE applies the LTE predicate on the "notes" field.
func NotesLTE(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldLTE(FieldNotes, v))
}
// NotesContains applies the Contains predicate on the "notes" field.
func NotesContains(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContains(FieldNotes, v))
}
// NotesHasPrefix applies the HasPrefix predicate on the "notes" field.
func NotesHasPrefix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasPrefix(FieldNotes, v))
}
// NotesHasSuffix applies the HasSuffix predicate on the "notes" field.
func NotesHasSuffix(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldHasSuffix(FieldNotes, v))
}
// NotesIsNil applies the IsNil predicate on the "notes" field.
func NotesIsNil() predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldIsNull(FieldNotes))
}
// NotesNotNil applies the NotNil predicate on the "notes" field.
func NotesNotNil() predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldNotNull(FieldNotes))
}
// NotesEqualFold applies the EqualFold predicate on the "notes" field.
func NotesEqualFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldEqualFold(FieldNotes, v))
}
// NotesContainsFold applies the ContainsFold predicate on the "notes" field.
func NotesContainsFold(v string) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.FieldContainsFold(FieldNotes, v))
}
// HasGroup applies the HasEdge predicate on the "group" edge.
func HasGroup() predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(func(s *sql.Selector) {
step := sqlgraph.NewStep(
sqlgraph.From(Table, FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, GroupTable, GroupColumn),
)
sqlgraph.HasNeighbors(s, step)
})
}
// HasGroupWith applies the HasEdge predicate on the "group" edge with a given conditions (other predicates).
func HasGroupWith(preds ...predicate.Group) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(func(s *sql.Selector) {
step := newGroupStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
for _, p := range preds {
p(s)
}
})
})
}
// And groups predicates with the AND operator between them.
func And(predicates ...predicate.CompositeModelRoute) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.AndPredicates(predicates...))
}
// Or groups predicates with the OR operator between them.
func Or(predicates ...predicate.CompositeModelRoute) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.OrPredicates(predicates...))
}
// Not applies the not operator on the given predicate.
func Not(p predicate.CompositeModelRoute) predicate.CompositeModelRoute {
return predicate.CompositeModelRoute(sql.NotPredicates(p))
}
File diff suppressed because it is too large Load Diff
+88
View 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/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// CompositeModelRouteDelete is the builder for deleting a CompositeModelRoute entity.
type CompositeModelRouteDelete struct {
config
hooks []Hook
mutation *CompositeModelRouteMutation
}
// Where appends a list predicates to the CompositeModelRouteDelete builder.
func (_d *CompositeModelRouteDelete) Where(ps ...predicate.CompositeModelRoute) *CompositeModelRouteDelete {
_d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query and returns how many vertices were deleted.
func (_d *CompositeModelRouteDelete) 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 *CompositeModelRouteDelete) ExecX(ctx context.Context) int {
n, err := _d.Exec(ctx)
if err != nil {
panic(err)
}
return n
}
func (_d *CompositeModelRouteDelete) sqlExec(ctx context.Context) (int, error) {
_spec := sqlgraph.NewDeleteSpec(compositemodelroute.Table, sqlgraph.NewFieldSpec(compositemodelroute.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
}
// CompositeModelRouteDeleteOne is the builder for deleting a single CompositeModelRoute entity.
type CompositeModelRouteDeleteOne struct {
_d *CompositeModelRouteDelete
}
// Where appends a list predicates to the CompositeModelRouteDelete builder.
func (_d *CompositeModelRouteDeleteOne) Where(ps ...predicate.CompositeModelRoute) *CompositeModelRouteDeleteOne {
_d._d.mutation.Where(ps...)
return _d
}
// Exec executes the deletion query.
func (_d *CompositeModelRouteDeleteOne) Exec(ctx context.Context) error {
n, err := _d._d.Exec(ctx)
switch {
case err != nil:
return err
case n == 0:
return &NotFoundError{compositemodelroute.Label}
default:
return nil
}
}
// ExecX is like Exec, but panics if an error occurs.
func (_d *CompositeModelRouteDeleteOne) ExecX(ctx context.Context) {
if err := _d.Exec(ctx); err != nil {
panic(err)
}
}
+643
View File
@@ -0,0 +1,643 @@
// Code generated by ent, DO NOT EDIT.
package ent
import (
"context"
"fmt"
"math"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/sqlgraph"
"entgo.io/ent/schema/field"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// CompositeModelRouteQuery is the builder for querying CompositeModelRoute entities.
type CompositeModelRouteQuery struct {
config
ctx *QueryContext
order []compositemodelroute.OrderOption
inters []Interceptor
predicates []predicate.CompositeModelRoute
withGroup *GroupQuery
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 CompositeModelRouteQuery builder.
func (_q *CompositeModelRouteQuery) Where(ps ...predicate.CompositeModelRoute) *CompositeModelRouteQuery {
_q.predicates = append(_q.predicates, ps...)
return _q
}
// Limit the number of records to be returned by this query.
func (_q *CompositeModelRouteQuery) Limit(limit int) *CompositeModelRouteQuery {
_q.ctx.Limit = &limit
return _q
}
// Offset to start from.
func (_q *CompositeModelRouteQuery) Offset(offset int) *CompositeModelRouteQuery {
_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 *CompositeModelRouteQuery) Unique(unique bool) *CompositeModelRouteQuery {
_q.ctx.Unique = &unique
return _q
}
// Order specifies how the records should be ordered.
func (_q *CompositeModelRouteQuery) Order(o ...compositemodelroute.OrderOption) *CompositeModelRouteQuery {
_q.order = append(_q.order, o...)
return _q
}
// QueryGroup chains the current query on the "group" edge.
func (_q *CompositeModelRouteQuery) QueryGroup() *GroupQuery {
query := (&GroupClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
selector := _q.sqlQuery(ctx)
if err := selector.Err(); err != nil {
return nil, err
}
step := sqlgraph.NewStep(
sqlgraph.From(compositemodelroute.Table, compositemodelroute.FieldID, selector),
sqlgraph.To(group.Table, group.FieldID),
sqlgraph.Edge(sqlgraph.M2O, false, compositemodelroute.GroupTable, compositemodelroute.GroupColumn),
)
fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step)
return fromU, nil
}
return query
}
// First returns the first CompositeModelRoute entity from the query.
// Returns a *NotFoundError when no CompositeModelRoute was found.
func (_q *CompositeModelRouteQuery) First(ctx context.Context) (*CompositeModelRoute, 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{compositemodelroute.Label}
}
return nodes[0], nil
}
// FirstX is like First, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) FirstX(ctx context.Context) *CompositeModelRoute {
node, err := _q.First(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return node
}
// FirstID returns the first CompositeModelRoute ID from the query.
// Returns a *NotFoundError when no CompositeModelRoute ID was found.
func (_q *CompositeModelRouteQuery) 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{compositemodelroute.Label}
return
}
return ids[0], nil
}
// FirstIDX is like FirstID, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) FirstIDX(ctx context.Context) int64 {
id, err := _q.FirstID(ctx)
if err != nil && !IsNotFound(err) {
panic(err)
}
return id
}
// Only returns a single CompositeModelRoute entity found by the query, ensuring it only returns one.
// Returns a *NotSingularError when more than one CompositeModelRoute entity is found.
// Returns a *NotFoundError when no CompositeModelRoute entities are found.
func (_q *CompositeModelRouteQuery) Only(ctx context.Context) (*CompositeModelRoute, 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{compositemodelroute.Label}
default:
return nil, &NotSingularError{compositemodelroute.Label}
}
}
// OnlyX is like Only, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) OnlyX(ctx context.Context) *CompositeModelRoute {
node, err := _q.Only(ctx)
if err != nil {
panic(err)
}
return node
}
// OnlyID is like Only, but returns the only CompositeModelRoute ID in the query.
// Returns a *NotSingularError when more than one CompositeModelRoute ID is found.
// Returns a *NotFoundError when no entities are found.
func (_q *CompositeModelRouteQuery) 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{compositemodelroute.Label}
default:
err = &NotSingularError{compositemodelroute.Label}
}
return
}
// OnlyIDX is like OnlyID, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) 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 CompositeModelRoutes.
func (_q *CompositeModelRouteQuery) All(ctx context.Context) ([]*CompositeModelRoute, error) {
ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)
if err := _q.prepareQuery(ctx); err != nil {
return nil, err
}
qr := querierAll[[]*CompositeModelRoute, *CompositeModelRouteQuery]()
return withInterceptors[[]*CompositeModelRoute](ctx, _q, qr, _q.inters)
}
// AllX is like All, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) AllX(ctx context.Context) []*CompositeModelRoute {
nodes, err := _q.All(ctx)
if err != nil {
panic(err)
}
return nodes
}
// IDs executes the query and returns a list of CompositeModelRoute IDs.
func (_q *CompositeModelRouteQuery) 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(compositemodelroute.FieldID).Scan(ctx, &ids); err != nil {
return nil, err
}
return ids, nil
}
// IDsX is like IDs, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) 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 *CompositeModelRouteQuery) 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[*CompositeModelRouteQuery](), _q.inters)
}
// CountX is like Count, but panics if an error occurs.
func (_q *CompositeModelRouteQuery) 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 *CompositeModelRouteQuery) 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 *CompositeModelRouteQuery) ExistX(ctx context.Context) bool {
exist, err := _q.Exist(ctx)
if err != nil {
panic(err)
}
return exist
}
// Clone returns a duplicate of the CompositeModelRouteQuery 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 *CompositeModelRouteQuery) Clone() *CompositeModelRouteQuery {
if _q == nil {
return nil
}
return &CompositeModelRouteQuery{
config: _q.config,
ctx: _q.ctx.Clone(),
order: append([]compositemodelroute.OrderOption{}, _q.order...),
inters: append([]Interceptor{}, _q.inters...),
predicates: append([]predicate.CompositeModelRoute{}, _q.predicates...),
withGroup: _q.withGroup.Clone(),
// clone intermediate query.
sql: _q.sql.Clone(),
path: _q.path,
}
}
// WithGroup tells the query-builder to eager-load the nodes that are connected to
// the "group" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *CompositeModelRouteQuery) WithGroup(opts ...func(*GroupQuery)) *CompositeModelRouteQuery {
query := (&GroupClient{config: _q.config}).Query()
for _, opt := range opts {
opt(query)
}
_q.withGroup = query
return _q
}
// GroupBy is used to group vertices by one or more fields/columns.
// It is often used with aggregate functions, like: count, max, mean, min, sum.
//
// Example:
//
// var v []struct {
// CreatedAt time.Time `json:"created_at,omitempty"`
// Count int `json:"count,omitempty"`
// }
//
// client.CompositeModelRoute.Query().
// GroupBy(compositemodelroute.FieldCreatedAt).
// Aggregate(ent.Count()).
// Scan(ctx, &v)
func (_q *CompositeModelRouteQuery) GroupBy(field string, fields ...string) *CompositeModelRouteGroupBy {
_q.ctx.Fields = append([]string{field}, fields...)
grbuild := &CompositeModelRouteGroupBy{build: _q}
grbuild.flds = &_q.ctx.Fields
grbuild.label = compositemodelroute.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.CompositeModelRoute.Query().
// Select(compositemodelroute.FieldCreatedAt).
// Scan(ctx, &v)
func (_q *CompositeModelRouteQuery) Select(fields ...string) *CompositeModelRouteSelect {
_q.ctx.Fields = append(_q.ctx.Fields, fields...)
sbuild := &CompositeModelRouteSelect{CompositeModelRouteQuery: _q}
sbuild.label = compositemodelroute.Label
sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan
return sbuild
}
// Aggregate returns a CompositeModelRouteSelect configured with the given aggregations.
func (_q *CompositeModelRouteQuery) Aggregate(fns ...AggregateFunc) *CompositeModelRouteSelect {
return _q.Select().Aggregate(fns...)
}
func (_q *CompositeModelRouteQuery) 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 !compositemodelroute.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 *CompositeModelRouteQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*CompositeModelRoute, error) {
var (
nodes = []*CompositeModelRoute{}
_spec = _q.querySpec()
loadedTypes = [1]bool{
_q.withGroup != nil,
}
)
_spec.ScanValues = func(columns []string) ([]any, error) {
return (*CompositeModelRoute).scanValues(nil, columns)
}
_spec.Assign = func(columns []string, values []any) error {
node := &CompositeModelRoute{config: _q.config}
nodes = append(nodes, node)
node.Edges.loadedTypes = loadedTypes
return node.assignValues(columns, values)
}
if len(_q.modifiers) > 0 {
_spec.Modifiers = _q.modifiers
}
for i := range hooks {
hooks[i](ctx, _spec)
}
if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {
return nil, err
}
if len(nodes) == 0 {
return nodes, nil
}
if query := _q.withGroup; query != nil {
if err := _q.loadGroup(ctx, query, nodes, nil,
func(n *CompositeModelRoute, e *Group) { n.Edges.Group = e }); err != nil {
return nil, err
}
}
return nodes, nil
}
func (_q *CompositeModelRouteQuery) loadGroup(ctx context.Context, query *GroupQuery, nodes []*CompositeModelRoute, init func(*CompositeModelRoute), assign func(*CompositeModelRoute, *Group)) error {
ids := make([]int64, 0, len(nodes))
nodeids := make(map[int64][]*CompositeModelRoute)
for i := range nodes {
fk := nodes[i].GroupID
if _, ok := nodeids[fk]; !ok {
ids = append(ids, fk)
}
nodeids[fk] = append(nodeids[fk], nodes[i])
}
if len(ids) == 0 {
return nil
}
query.Where(group.IDIn(ids...))
neighbors, err := query.All(ctx)
if err != nil {
return err
}
for _, n := range neighbors {
nodes, ok := nodeids[n.ID]
if !ok {
return fmt.Errorf(`unexpected foreign-key "group_id" returned %v`, n.ID)
}
for i := range nodes {
assign(nodes[i], n)
}
}
return nil
}
func (_q *CompositeModelRouteQuery) 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 *CompositeModelRouteQuery) querySpec() *sqlgraph.QuerySpec {
_spec := sqlgraph.NewQuerySpec(compositemodelroute.Table, compositemodelroute.Columns, sqlgraph.NewFieldSpec(compositemodelroute.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, compositemodelroute.FieldID)
for i := range fields {
if fields[i] != compositemodelroute.FieldID {
_spec.Node.Columns = append(_spec.Node.Columns, fields[i])
}
}
if _q.withGroup != nil {
_spec.Node.AddColumnOnce(compositemodelroute.FieldGroupID)
}
}
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 *CompositeModelRouteQuery) sqlQuery(ctx context.Context) *sql.Selector {
builder := sql.Dialect(_q.driver.Dialect())
t1 := builder.Table(compositemodelroute.Table)
columns := _q.ctx.Fields
if len(columns) == 0 {
columns = compositemodelroute.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 *CompositeModelRouteQuery) ForUpdate(opts ...sql.LockOption) *CompositeModelRouteQuery {
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 *CompositeModelRouteQuery) ForShare(opts ...sql.LockOption) *CompositeModelRouteQuery {
if _q.driver.Dialect() == dialect.Postgres {
_q.Unique(false)
}
_q.modifiers = append(_q.modifiers, func(s *sql.Selector) {
s.ForShare(opts...)
})
return _q
}
// CompositeModelRouteGroupBy is the group-by builder for CompositeModelRoute entities.
type CompositeModelRouteGroupBy struct {
selector
build *CompositeModelRouteQuery
}
// Aggregate adds the given aggregation functions to the group-by query.
func (_g *CompositeModelRouteGroupBy) Aggregate(fns ...AggregateFunc) *CompositeModelRouteGroupBy {
_g.fns = append(_g.fns, fns...)
return _g
}
// Scan applies the selector query and scans the result into the given value.
func (_g *CompositeModelRouteGroupBy) 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[*CompositeModelRouteQuery, *CompositeModelRouteGroupBy](ctx, _g.build, _g, _g.build.inters, v)
}
func (_g *CompositeModelRouteGroupBy) sqlScan(ctx context.Context, root *CompositeModelRouteQuery, 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)
}
// CompositeModelRouteSelect is the builder for selecting fields of CompositeModelRoute entities.
type CompositeModelRouteSelect struct {
*CompositeModelRouteQuery
selector
}
// Aggregate adds the given aggregation functions to the selector query.
func (_s *CompositeModelRouteSelect) Aggregate(fns ...AggregateFunc) *CompositeModelRouteSelect {
_s.fns = append(_s.fns, fns...)
return _s
}
// Scan applies the selector query and scans the result into the given value.
func (_s *CompositeModelRouteSelect) 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[*CompositeModelRouteQuery, *CompositeModelRouteSelect](ctx, _s.CompositeModelRouteQuery, _s, _s.inters, v)
}
func (_s *CompositeModelRouteSelect) sqlScan(ctx context.Context, root *CompositeModelRouteQuery, 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)
}
+767
View File
@@ -0,0 +1,767 @@
// 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/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/predicate"
)
// CompositeModelRouteUpdate is the builder for updating CompositeModelRoute entities.
type CompositeModelRouteUpdate struct {
config
hooks []Hook
mutation *CompositeModelRouteMutation
}
// Where appends a list predicates to the CompositeModelRouteUpdate builder.
func (_u *CompositeModelRouteUpdate) Where(ps ...predicate.CompositeModelRoute) *CompositeModelRouteUpdate {
_u.mutation.Where(ps...)
return _u
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *CompositeModelRouteUpdate) SetUpdatedAt(v time.Time) *CompositeModelRouteUpdate {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetDeletedAt sets the "deleted_at" field.
func (_u *CompositeModelRouteUpdate) SetDeletedAt(v time.Time) *CompositeModelRouteUpdate {
_u.mutation.SetDeletedAt(v)
return _u
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableDeletedAt(v *time.Time) *CompositeModelRouteUpdate {
if v != nil {
_u.SetDeletedAt(*v)
}
return _u
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (_u *CompositeModelRouteUpdate) ClearDeletedAt() *CompositeModelRouteUpdate {
_u.mutation.ClearDeletedAt()
return _u
}
// SetGroupID sets the "group_id" field.
func (_u *CompositeModelRouteUpdate) SetGroupID(v int64) *CompositeModelRouteUpdate {
_u.mutation.SetGroupID(v)
return _u
}
// SetNillableGroupID sets the "group_id" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableGroupID(v *int64) *CompositeModelRouteUpdate {
if v != nil {
_u.SetGroupID(*v)
}
return _u
}
// SetPublicModel sets the "public_model" field.
func (_u *CompositeModelRouteUpdate) SetPublicModel(v string) *CompositeModelRouteUpdate {
_u.mutation.SetPublicModel(v)
return _u
}
// SetNillablePublicModel sets the "public_model" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillablePublicModel(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetPublicModel(*v)
}
return _u
}
// SetMatchType sets the "match_type" field.
func (_u *CompositeModelRouteUpdate) SetMatchType(v string) *CompositeModelRouteUpdate {
_u.mutation.SetMatchType(v)
return _u
}
// SetNillableMatchType sets the "match_type" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableMatchType(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetMatchType(*v)
}
return _u
}
// SetTargetPlatform sets the "target_platform" field.
func (_u *CompositeModelRouteUpdate) SetTargetPlatform(v string) *CompositeModelRouteUpdate {
_u.mutation.SetTargetPlatform(v)
return _u
}
// SetNillableTargetPlatform sets the "target_platform" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableTargetPlatform(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetTargetPlatform(*v)
}
return _u
}
// SetUpstreamModel sets the "upstream_model" field.
func (_u *CompositeModelRouteUpdate) SetUpstreamModel(v string) *CompositeModelRouteUpdate {
_u.mutation.SetUpstreamModel(v)
return _u
}
// SetNillableUpstreamModel sets the "upstream_model" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableUpstreamModel(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetUpstreamModel(*v)
}
return _u
}
// SetEndpoint sets the "endpoint" field.
func (_u *CompositeModelRouteUpdate) SetEndpoint(v string) *CompositeModelRouteUpdate {
_u.mutation.SetEndpoint(v)
return _u
}
// SetNillableEndpoint sets the "endpoint" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableEndpoint(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetEndpoint(*v)
}
return _u
}
// SetPriority sets the "priority" field.
func (_u *CompositeModelRouteUpdate) SetPriority(v int) *CompositeModelRouteUpdate {
_u.mutation.ResetPriority()
_u.mutation.SetPriority(v)
return _u
}
// SetNillablePriority sets the "priority" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillablePriority(v *int) *CompositeModelRouteUpdate {
if v != nil {
_u.SetPriority(*v)
}
return _u
}
// AddPriority adds value to the "priority" field.
func (_u *CompositeModelRouteUpdate) AddPriority(v int) *CompositeModelRouteUpdate {
_u.mutation.AddPriority(v)
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *CompositeModelRouteUpdate) SetEnabled(v bool) *CompositeModelRouteUpdate {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableEnabled(v *bool) *CompositeModelRouteUpdate {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetNotes sets the "notes" field.
func (_u *CompositeModelRouteUpdate) SetNotes(v string) *CompositeModelRouteUpdate {
_u.mutation.SetNotes(v)
return _u
}
// SetNillableNotes sets the "notes" field if the given value is not nil.
func (_u *CompositeModelRouteUpdate) SetNillableNotes(v *string) *CompositeModelRouteUpdate {
if v != nil {
_u.SetNotes(*v)
}
return _u
}
// ClearNotes clears the value of the "notes" field.
func (_u *CompositeModelRouteUpdate) ClearNotes() *CompositeModelRouteUpdate {
_u.mutation.ClearNotes()
return _u
}
// SetGroup sets the "group" edge to the Group entity.
func (_u *CompositeModelRouteUpdate) SetGroup(v *Group) *CompositeModelRouteUpdate {
return _u.SetGroupID(v.ID)
}
// Mutation returns the CompositeModelRouteMutation object of the builder.
func (_u *CompositeModelRouteUpdate) Mutation() *CompositeModelRouteMutation {
return _u.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (_u *CompositeModelRouteUpdate) ClearGroup() *CompositeModelRouteUpdate {
_u.mutation.ClearGroup()
return _u
}
// Save executes the query and returns the number of nodes affected by the update operation.
func (_u *CompositeModelRouteUpdate) Save(ctx context.Context) (int, error) {
if err := _u.defaults(); err != nil {
return 0, err
}
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *CompositeModelRouteUpdate) SaveX(ctx context.Context) int {
affected, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return affected
}
// Exec executes the query.
func (_u *CompositeModelRouteUpdate) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *CompositeModelRouteUpdate) 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 *CompositeModelRouteUpdate) defaults() error {
if _, ok := _u.mutation.UpdatedAt(); !ok {
if compositemodelroute.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized compositemodelroute.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := compositemodelroute.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (_u *CompositeModelRouteUpdate) check() error {
if v, ok := _u.mutation.PublicModel(); ok {
if err := compositemodelroute.PublicModelValidator(v); err != nil {
return &ValidationError{Name: "public_model", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.public_model": %w`, err)}
}
}
if v, ok := _u.mutation.MatchType(); ok {
if err := compositemodelroute.MatchTypeValidator(v); err != nil {
return &ValidationError{Name: "match_type", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.match_type": %w`, err)}
}
}
if v, ok := _u.mutation.TargetPlatform(); ok {
if err := compositemodelroute.TargetPlatformValidator(v); err != nil {
return &ValidationError{Name: "target_platform", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.target_platform": %w`, err)}
}
}
if v, ok := _u.mutation.UpstreamModel(); ok {
if err := compositemodelroute.UpstreamModelValidator(v); err != nil {
return &ValidationError{Name: "upstream_model", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.upstream_model": %w`, err)}
}
}
if v, ok := _u.mutation.Endpoint(); ok {
if err := compositemodelroute.EndpointValidator(v); err != nil {
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.endpoint": %w`, err)}
}
}
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "CompositeModelRoute.group"`)
}
return nil
}
func (_u *CompositeModelRouteUpdate) sqlSave(ctx context.Context) (_node int, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(compositemodelroute.Table, compositemodelroute.Columns, sqlgraph.NewFieldSpec(compositemodelroute.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(compositemodelroute.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.DeletedAt(); ok {
_spec.SetField(compositemodelroute.FieldDeletedAt, field.TypeTime, value)
}
if _u.mutation.DeletedAtCleared() {
_spec.ClearField(compositemodelroute.FieldDeletedAt, field.TypeTime)
}
if value, ok := _u.mutation.PublicModel(); ok {
_spec.SetField(compositemodelroute.FieldPublicModel, field.TypeString, value)
}
if value, ok := _u.mutation.MatchType(); ok {
_spec.SetField(compositemodelroute.FieldMatchType, field.TypeString, value)
}
if value, ok := _u.mutation.TargetPlatform(); ok {
_spec.SetField(compositemodelroute.FieldTargetPlatform, field.TypeString, value)
}
if value, ok := _u.mutation.UpstreamModel(); ok {
_spec.SetField(compositemodelroute.FieldUpstreamModel, field.TypeString, value)
}
if value, ok := _u.mutation.Endpoint(); ok {
_spec.SetField(compositemodelroute.FieldEndpoint, field.TypeString, value)
}
if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(compositemodelroute.FieldPriority, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedPriority(); ok {
_spec.AddField(compositemodelroute.FieldPriority, field.TypeInt, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(compositemodelroute.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.Notes(); ok {
_spec.SetField(compositemodelroute.FieldNotes, field.TypeString, value)
}
if _u.mutation.NotesCleared() {
_spec.ClearField(compositemodelroute.FieldNotes, field.TypeString)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: false,
Table: compositemodelroute.GroupTable,
Columns: []string{compositemodelroute.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: false,
Table: compositemodelroute.GroupTable,
Columns: []string{compositemodelroute.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {
if _, ok := err.(*sqlgraph.NotFoundError); ok {
err = &NotFoundError{compositemodelroute.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return 0, err
}
_u.mutation.done = true
return _node, nil
}
// CompositeModelRouteUpdateOne is the builder for updating a single CompositeModelRoute entity.
type CompositeModelRouteUpdateOne struct {
config
fields []string
hooks []Hook
mutation *CompositeModelRouteMutation
}
// SetUpdatedAt sets the "updated_at" field.
func (_u *CompositeModelRouteUpdateOne) SetUpdatedAt(v time.Time) *CompositeModelRouteUpdateOne {
_u.mutation.SetUpdatedAt(v)
return _u
}
// SetDeletedAt sets the "deleted_at" field.
func (_u *CompositeModelRouteUpdateOne) SetDeletedAt(v time.Time) *CompositeModelRouteUpdateOne {
_u.mutation.SetDeletedAt(v)
return _u
}
// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableDeletedAt(v *time.Time) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetDeletedAt(*v)
}
return _u
}
// ClearDeletedAt clears the value of the "deleted_at" field.
func (_u *CompositeModelRouteUpdateOne) ClearDeletedAt() *CompositeModelRouteUpdateOne {
_u.mutation.ClearDeletedAt()
return _u
}
// SetGroupID sets the "group_id" field.
func (_u *CompositeModelRouteUpdateOne) SetGroupID(v int64) *CompositeModelRouteUpdateOne {
_u.mutation.SetGroupID(v)
return _u
}
// SetNillableGroupID sets the "group_id" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableGroupID(v *int64) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetGroupID(*v)
}
return _u
}
// SetPublicModel sets the "public_model" field.
func (_u *CompositeModelRouteUpdateOne) SetPublicModel(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetPublicModel(v)
return _u
}
// SetNillablePublicModel sets the "public_model" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillablePublicModel(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetPublicModel(*v)
}
return _u
}
// SetMatchType sets the "match_type" field.
func (_u *CompositeModelRouteUpdateOne) SetMatchType(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetMatchType(v)
return _u
}
// SetNillableMatchType sets the "match_type" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableMatchType(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetMatchType(*v)
}
return _u
}
// SetTargetPlatform sets the "target_platform" field.
func (_u *CompositeModelRouteUpdateOne) SetTargetPlatform(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetTargetPlatform(v)
return _u
}
// SetNillableTargetPlatform sets the "target_platform" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableTargetPlatform(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetTargetPlatform(*v)
}
return _u
}
// SetUpstreamModel sets the "upstream_model" field.
func (_u *CompositeModelRouteUpdateOne) SetUpstreamModel(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetUpstreamModel(v)
return _u
}
// SetNillableUpstreamModel sets the "upstream_model" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableUpstreamModel(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetUpstreamModel(*v)
}
return _u
}
// SetEndpoint sets the "endpoint" field.
func (_u *CompositeModelRouteUpdateOne) SetEndpoint(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetEndpoint(v)
return _u
}
// SetNillableEndpoint sets the "endpoint" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableEndpoint(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetEndpoint(*v)
}
return _u
}
// SetPriority sets the "priority" field.
func (_u *CompositeModelRouteUpdateOne) SetPriority(v int) *CompositeModelRouteUpdateOne {
_u.mutation.ResetPriority()
_u.mutation.SetPriority(v)
return _u
}
// SetNillablePriority sets the "priority" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillablePriority(v *int) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetPriority(*v)
}
return _u
}
// AddPriority adds value to the "priority" field.
func (_u *CompositeModelRouteUpdateOne) AddPriority(v int) *CompositeModelRouteUpdateOne {
_u.mutation.AddPriority(v)
return _u
}
// SetEnabled sets the "enabled" field.
func (_u *CompositeModelRouteUpdateOne) SetEnabled(v bool) *CompositeModelRouteUpdateOne {
_u.mutation.SetEnabled(v)
return _u
}
// SetNillableEnabled sets the "enabled" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableEnabled(v *bool) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetEnabled(*v)
}
return _u
}
// SetNotes sets the "notes" field.
func (_u *CompositeModelRouteUpdateOne) SetNotes(v string) *CompositeModelRouteUpdateOne {
_u.mutation.SetNotes(v)
return _u
}
// SetNillableNotes sets the "notes" field if the given value is not nil.
func (_u *CompositeModelRouteUpdateOne) SetNillableNotes(v *string) *CompositeModelRouteUpdateOne {
if v != nil {
_u.SetNotes(*v)
}
return _u
}
// ClearNotes clears the value of the "notes" field.
func (_u *CompositeModelRouteUpdateOne) ClearNotes() *CompositeModelRouteUpdateOne {
_u.mutation.ClearNotes()
return _u
}
// SetGroup sets the "group" edge to the Group entity.
func (_u *CompositeModelRouteUpdateOne) SetGroup(v *Group) *CompositeModelRouteUpdateOne {
return _u.SetGroupID(v.ID)
}
// Mutation returns the CompositeModelRouteMutation object of the builder.
func (_u *CompositeModelRouteUpdateOne) Mutation() *CompositeModelRouteMutation {
return _u.mutation
}
// ClearGroup clears the "group" edge to the Group entity.
func (_u *CompositeModelRouteUpdateOne) ClearGroup() *CompositeModelRouteUpdateOne {
_u.mutation.ClearGroup()
return _u
}
// Where appends a list predicates to the CompositeModelRouteUpdate builder.
func (_u *CompositeModelRouteUpdateOne) Where(ps ...predicate.CompositeModelRoute) *CompositeModelRouteUpdateOne {
_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 *CompositeModelRouteUpdateOne) Select(field string, fields ...string) *CompositeModelRouteUpdateOne {
_u.fields = append([]string{field}, fields...)
return _u
}
// Save executes the query and returns the updated CompositeModelRoute entity.
func (_u *CompositeModelRouteUpdateOne) Save(ctx context.Context) (*CompositeModelRoute, error) {
if err := _u.defaults(); err != nil {
return nil, err
}
return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)
}
// SaveX is like Save, but panics if an error occurs.
func (_u *CompositeModelRouteUpdateOne) SaveX(ctx context.Context) *CompositeModelRoute {
node, err := _u.Save(ctx)
if err != nil {
panic(err)
}
return node
}
// Exec executes the query on the entity.
func (_u *CompositeModelRouteUpdateOne) Exec(ctx context.Context) error {
_, err := _u.Save(ctx)
return err
}
// ExecX is like Exec, but panics if an error occurs.
func (_u *CompositeModelRouteUpdateOne) 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 *CompositeModelRouteUpdateOne) defaults() error {
if _, ok := _u.mutation.UpdatedAt(); !ok {
if compositemodelroute.UpdateDefaultUpdatedAt == nil {
return fmt.Errorf("ent: uninitialized compositemodelroute.UpdateDefaultUpdatedAt (forgotten import ent/runtime?)")
}
v := compositemodelroute.UpdateDefaultUpdatedAt()
_u.mutation.SetUpdatedAt(v)
}
return nil
}
// check runs all checks and user-defined validators on the builder.
func (_u *CompositeModelRouteUpdateOne) check() error {
if v, ok := _u.mutation.PublicModel(); ok {
if err := compositemodelroute.PublicModelValidator(v); err != nil {
return &ValidationError{Name: "public_model", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.public_model": %w`, err)}
}
}
if v, ok := _u.mutation.MatchType(); ok {
if err := compositemodelroute.MatchTypeValidator(v); err != nil {
return &ValidationError{Name: "match_type", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.match_type": %w`, err)}
}
}
if v, ok := _u.mutation.TargetPlatform(); ok {
if err := compositemodelroute.TargetPlatformValidator(v); err != nil {
return &ValidationError{Name: "target_platform", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.target_platform": %w`, err)}
}
}
if v, ok := _u.mutation.UpstreamModel(); ok {
if err := compositemodelroute.UpstreamModelValidator(v); err != nil {
return &ValidationError{Name: "upstream_model", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.upstream_model": %w`, err)}
}
}
if v, ok := _u.mutation.Endpoint(); ok {
if err := compositemodelroute.EndpointValidator(v); err != nil {
return &ValidationError{Name: "endpoint", err: fmt.Errorf(`ent: validator failed for field "CompositeModelRoute.endpoint": %w`, err)}
}
}
if _u.mutation.GroupCleared() && len(_u.mutation.GroupIDs()) > 0 {
return errors.New(`ent: clearing a required unique edge "CompositeModelRoute.group"`)
}
return nil
}
func (_u *CompositeModelRouteUpdateOne) sqlSave(ctx context.Context) (_node *CompositeModelRoute, err error) {
if err := _u.check(); err != nil {
return _node, err
}
_spec := sqlgraph.NewUpdateSpec(compositemodelroute.Table, compositemodelroute.Columns, sqlgraph.NewFieldSpec(compositemodelroute.FieldID, field.TypeInt64))
id, ok := _u.mutation.ID()
if !ok {
return nil, &ValidationError{Name: "id", err: errors.New(`ent: missing "CompositeModelRoute.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, compositemodelroute.FieldID)
for _, f := range fields {
if !compositemodelroute.ValidColumn(f) {
return nil, &ValidationError{Name: f, err: fmt.Errorf("ent: invalid field %q for query", f)}
}
if f != compositemodelroute.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(compositemodelroute.FieldUpdatedAt, field.TypeTime, value)
}
if value, ok := _u.mutation.DeletedAt(); ok {
_spec.SetField(compositemodelroute.FieldDeletedAt, field.TypeTime, value)
}
if _u.mutation.DeletedAtCleared() {
_spec.ClearField(compositemodelroute.FieldDeletedAt, field.TypeTime)
}
if value, ok := _u.mutation.PublicModel(); ok {
_spec.SetField(compositemodelroute.FieldPublicModel, field.TypeString, value)
}
if value, ok := _u.mutation.MatchType(); ok {
_spec.SetField(compositemodelroute.FieldMatchType, field.TypeString, value)
}
if value, ok := _u.mutation.TargetPlatform(); ok {
_spec.SetField(compositemodelroute.FieldTargetPlatform, field.TypeString, value)
}
if value, ok := _u.mutation.UpstreamModel(); ok {
_spec.SetField(compositemodelroute.FieldUpstreamModel, field.TypeString, value)
}
if value, ok := _u.mutation.Endpoint(); ok {
_spec.SetField(compositemodelroute.FieldEndpoint, field.TypeString, value)
}
if value, ok := _u.mutation.Priority(); ok {
_spec.SetField(compositemodelroute.FieldPriority, field.TypeInt, value)
}
if value, ok := _u.mutation.AddedPriority(); ok {
_spec.AddField(compositemodelroute.FieldPriority, field.TypeInt, value)
}
if value, ok := _u.mutation.Enabled(); ok {
_spec.SetField(compositemodelroute.FieldEnabled, field.TypeBool, value)
}
if value, ok := _u.mutation.Notes(); ok {
_spec.SetField(compositemodelroute.FieldNotes, field.TypeString, value)
}
if _u.mutation.NotesCleared() {
_spec.ClearField(compositemodelroute.FieldNotes, field.TypeString)
}
if _u.mutation.GroupCleared() {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: false,
Table: compositemodelroute.GroupTable,
Columns: []string{compositemodelroute.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeInt64),
},
}
_spec.Edges.Clear = append(_spec.Edges.Clear, edge)
}
if nodes := _u.mutation.GroupIDs(); len(nodes) > 0 {
edge := &sqlgraph.EdgeSpec{
Rel: sqlgraph.M2O,
Inverse: false,
Table: compositemodelroute.GroupTable,
Columns: []string{compositemodelroute.GroupColumn},
Bidi: false,
Target: &sqlgraph.EdgeTarget{
IDSpec: sqlgraph.NewFieldSpec(group.FieldID, field.TypeInt64),
},
}
for _, k := range nodes {
edge.Target.Nodes = append(edge.Target.Nodes, k)
}
_spec.Edges.Add = append(_spec.Edges.Add, edge)
}
_node = &CompositeModelRoute{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{compositemodelroute.Label}
} else if sqlgraph.IsConstraintError(err) {
err = &ConstraintError{msg: err.Error(), wrap: err}
}
return nil, err
}
_u.mutation.done = true
return _node, nil
}
+2
View File
@@ -26,6 +26,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
@@ -124,6 +125,7 @@ func checkColumn(t, c string) error {
channelmonitordailyrollup.Table: channelmonitordailyrollup.ValidColumn,
channelmonitorhistory.Table: channelmonitorhistory.ValidColumn,
channelmonitorrequesttemplate.Table: channelmonitorrequesttemplate.ValidColumn,
compositemodelroute.Table: compositemodelroute.ValidColumn,
errorpassthroughrule.Table: errorpassthroughrule.ValidColumn,
group.Table: group.ValidColumn,
idempotencyrecord.Table: idempotencyrecord.ValidColumn,
+12
View File
@@ -177,6 +177,18 @@ func (f ChannelMonitorRequestTemplateFunc) Mutate(ctx context.Context, m ent.Mut
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.ChannelMonitorRequestTemplateMutation", m)
}
// The CompositeModelRouteFunc type is an adapter to allow the use of ordinary
// function as CompositeModelRoute mutator.
type CompositeModelRouteFunc func(context.Context, *ent.CompositeModelRouteMutation) (ent.Value, error)
// Mutate calls f(ctx, m).
func (f CompositeModelRouteFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) {
if mv, ok := m.(*ent.CompositeModelRouteMutation); ok {
return f(ctx, mv)
}
return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.CompositeModelRouteMutation", m)
}
// The ErrorPassthroughRuleFunc type is an adapter to allow the use of ordinary
// function as ErrorPassthroughRule mutator.
type ErrorPassthroughRuleFunc func(context.Context, *ent.ErrorPassthroughRuleMutation) (ent.Value, error)
+30
View File
@@ -22,6 +22,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
@@ -483,6 +484,33 @@ func (f TraverseChannelMonitorRequestTemplate) Traverse(ctx context.Context, q e
return fmt.Errorf("unexpected query type %T. expect *ent.ChannelMonitorRequestTemplateQuery", q)
}
// The CompositeModelRouteFunc type is an adapter to allow the use of ordinary function as a Querier.
type CompositeModelRouteFunc func(context.Context, *ent.CompositeModelRouteQuery) (ent.Value, error)
// Query calls f(ctx, q).
func (f CompositeModelRouteFunc) Query(ctx context.Context, q ent.Query) (ent.Value, error) {
if q, ok := q.(*ent.CompositeModelRouteQuery); ok {
return f(ctx, q)
}
return nil, fmt.Errorf("unexpected query type %T. expect *ent.CompositeModelRouteQuery", q)
}
// The TraverseCompositeModelRoute type is an adapter to allow the use of ordinary function as Traverser.
type TraverseCompositeModelRoute func(context.Context, *ent.CompositeModelRouteQuery) error
// Intercept is a dummy implementation of Intercept that returns the next Querier in the pipeline.
func (f TraverseCompositeModelRoute) Intercept(next ent.Querier) ent.Querier {
return next
}
// Traverse calls f(ctx, q).
func (f TraverseCompositeModelRoute) Traverse(ctx context.Context, q ent.Query) error {
if q, ok := q.(*ent.CompositeModelRouteQuery); ok {
return f(ctx, q)
}
return fmt.Errorf("unexpected query type %T. expect *ent.CompositeModelRouteQuery", q)
}
// The ErrorPassthroughRuleFunc type is an adapter to allow the use of ordinary function as a Querier.
type ErrorPassthroughRuleFunc func(context.Context, *ent.ErrorPassthroughRuleQuery) (ent.Value, error)
@@ -1162,6 +1190,8 @@ func NewQuery(q ent.Query) (Query, error) {
return &query[*ent.ChannelMonitorHistoryQuery, predicate.ChannelMonitorHistory, channelmonitorhistory.OrderOption]{typ: ent.TypeChannelMonitorHistory, tq: q}, nil
case *ent.ChannelMonitorRequestTemplateQuery:
return &query[*ent.ChannelMonitorRequestTemplateQuery, predicate.ChannelMonitorRequestTemplate, channelmonitorrequesttemplate.OrderOption]{typ: ent.TypeChannelMonitorRequestTemplate, tq: q}, nil
case *ent.CompositeModelRouteQuery:
return &query[*ent.CompositeModelRouteQuery, predicate.CompositeModelRoute, compositemodelroute.OrderOption]{typ: ent.TypeCompositeModelRoute, tq: q}, nil
case *ent.ErrorPassthroughRuleQuery:
return &query[*ent.ErrorPassthroughRuleQuery, predicate.ErrorPassthroughRule, errorpassthroughrule.OrderOption]{typ: ent.TypeErrorPassthroughRule, tq: q}, nil
case *ent.GroupQuery:
+67
View File
@@ -793,6 +793,68 @@ var (
},
},
}
// CompositeModelRoutesColumns holds the columns for the "composite_model_routes" table.
CompositeModelRoutesColumns = []*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: "deleted_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
{Name: "public_model", Type: field.TypeString, Size: 200},
{Name: "match_type", Type: field.TypeString, Size: 20, Default: "exact"},
{Name: "target_platform", Type: field.TypeString, Size: 50, Default: "openai"},
{Name: "upstream_model", Type: field.TypeString, Size: 200, Default: ""},
{Name: "endpoint", Type: field.TypeString, Size: 50, Default: "any"},
{Name: "priority", Type: field.TypeInt, Default: 100},
{Name: "enabled", Type: field.TypeBool, Default: true},
{Name: "notes", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
{Name: "group_id", Type: field.TypeInt64},
}
// CompositeModelRoutesTable holds the schema information for the "composite_model_routes" table.
CompositeModelRoutesTable = &schema.Table{
Name: "composite_model_routes",
Columns: CompositeModelRoutesColumns,
PrimaryKey: []*schema.Column{CompositeModelRoutesColumns[0]},
ForeignKeys: []*schema.ForeignKey{
{
Symbol: "composite_model_routes_groups_group",
Columns: []*schema.Column{CompositeModelRoutesColumns[12]},
RefColumns: []*schema.Column{GroupsColumns[0]},
OnDelete: schema.NoAction,
},
},
Indexes: []*schema.Index{
{
Name: "compositemodelroute_group_id",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[12]},
},
{
Name: "compositemodelroute_group_id_enabled",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[12], CompositeModelRoutesColumns[10]},
},
{
Name: "compositemodelroute_group_id_endpoint",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[12], CompositeModelRoutesColumns[8]},
},
{
Name: "compositemodelroute_group_id_target_platform",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[12], CompositeModelRoutesColumns[6]},
},
{
Name: "compositemodelroute_deleted_at",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[3]},
},
{
Name: "compositemodelroute_priority",
Unique: false,
Columns: []*schema.Column{CompositeModelRoutesColumns[9]},
},
},
}
// ErrorPassthroughRulesColumns holds the columns for the "error_passthrough_rules" table.
ErrorPassthroughRulesColumns = []*schema.Column{
{Name: "id", Type: field.TypeInt64, Increment: true},
@@ -2014,6 +2076,7 @@ var (
ChannelMonitorDailyRollupsTable,
ChannelMonitorHistoriesTable,
ChannelMonitorRequestTemplatesTable,
CompositeModelRoutesTable,
ErrorPassthroughRulesTable,
GroupsTable,
IdempotencyRecordsTable,
@@ -2097,6 +2160,10 @@ func init() {
ChannelMonitorRequestTemplatesTable.Annotation = &entsql.Annotation{
Table: "channel_monitor_request_templates",
}
CompositeModelRoutesTable.ForeignKeys[0].RefTable = GroupsTable
CompositeModelRoutesTable.Annotation = &entsql.Annotation{
Table: "composite_model_routes",
}
ErrorPassthroughRulesTable.Annotation = &entsql.Annotation{
Table: "error_passthrough_rules",
}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -48,6 +48,9 @@ type ChannelMonitorHistory func(*sql.Selector)
// ChannelMonitorRequestTemplate is the predicate function for channelmonitorrequesttemplate builders.
type ChannelMonitorRequestTemplate func(*sql.Selector)
// CompositeModelRoute is the predicate function for compositemodelroute builders.
type CompositeModelRoute func(*sql.Selector)
// ErrorPassthroughRule is the predicate function for errorpassthroughrule builders.
type ErrorPassthroughRule func(*sql.Selector)
+70
View File
@@ -19,6 +19,7 @@ import (
"github.com/Wei-Shaw/sub2api/ent/channelmonitordailyrollup"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorhistory"
"github.com/Wei-Shaw/sub2api/ent/channelmonitorrequesttemplate"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/ent/errorpassthroughrule"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/idempotencyrecord"
@@ -865,6 +866,75 @@ func init() {
channelmonitorrequesttemplate.DefaultBodyOverrideMode = channelmonitorrequesttemplateDescBodyOverrideMode.Default.(string)
// channelmonitorrequesttemplate.BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
channelmonitorrequesttemplate.BodyOverrideModeValidator = channelmonitorrequesttemplateDescBodyOverrideMode.Validators[0].(func(string) error)
compositemodelrouteMixin := schema.CompositeModelRoute{}.Mixin()
compositemodelrouteMixinHooks1 := compositemodelrouteMixin[1].Hooks()
compositemodelroute.Hooks[0] = compositemodelrouteMixinHooks1[0]
compositemodelrouteMixinInters1 := compositemodelrouteMixin[1].Interceptors()
compositemodelroute.Interceptors[0] = compositemodelrouteMixinInters1[0]
compositemodelrouteMixinFields0 := compositemodelrouteMixin[0].Fields()
_ = compositemodelrouteMixinFields0
compositemodelrouteFields := schema.CompositeModelRoute{}.Fields()
_ = compositemodelrouteFields
// compositemodelrouteDescCreatedAt is the schema descriptor for created_at field.
compositemodelrouteDescCreatedAt := compositemodelrouteMixinFields0[0].Descriptor()
// compositemodelroute.DefaultCreatedAt holds the default value on creation for the created_at field.
compositemodelroute.DefaultCreatedAt = compositemodelrouteDescCreatedAt.Default.(func() time.Time)
// compositemodelrouteDescUpdatedAt is the schema descriptor for updated_at field.
compositemodelrouteDescUpdatedAt := compositemodelrouteMixinFields0[1].Descriptor()
// compositemodelroute.DefaultUpdatedAt holds the default value on creation for the updated_at field.
compositemodelroute.DefaultUpdatedAt = compositemodelrouteDescUpdatedAt.Default.(func() time.Time)
// compositemodelroute.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field.
compositemodelroute.UpdateDefaultUpdatedAt = compositemodelrouteDescUpdatedAt.UpdateDefault.(func() time.Time)
// compositemodelrouteDescPublicModel is the schema descriptor for public_model field.
compositemodelrouteDescPublicModel := compositemodelrouteFields[1].Descriptor()
// compositemodelroute.PublicModelValidator is a validator for the "public_model" field. It is called by the builders before save.
compositemodelroute.PublicModelValidator = func() func(string) error {
validators := compositemodelrouteDescPublicModel.Validators
fns := [...]func(string) error{
validators[0].(func(string) error),
validators[1].(func(string) error),
}
return func(public_model string) error {
for _, fn := range fns {
if err := fn(public_model); err != nil {
return err
}
}
return nil
}
}()
// compositemodelrouteDescMatchType is the schema descriptor for match_type field.
compositemodelrouteDescMatchType := compositemodelrouteFields[2].Descriptor()
// compositemodelroute.DefaultMatchType holds the default value on creation for the match_type field.
compositemodelroute.DefaultMatchType = compositemodelrouteDescMatchType.Default.(string)
// compositemodelroute.MatchTypeValidator is a validator for the "match_type" field. It is called by the builders before save.
compositemodelroute.MatchTypeValidator = compositemodelrouteDescMatchType.Validators[0].(func(string) error)
// compositemodelrouteDescTargetPlatform is the schema descriptor for target_platform field.
compositemodelrouteDescTargetPlatform := compositemodelrouteFields[3].Descriptor()
// compositemodelroute.DefaultTargetPlatform holds the default value on creation for the target_platform field.
compositemodelroute.DefaultTargetPlatform = compositemodelrouteDescTargetPlatform.Default.(string)
// compositemodelroute.TargetPlatformValidator is a validator for the "target_platform" field. It is called by the builders before save.
compositemodelroute.TargetPlatformValidator = compositemodelrouteDescTargetPlatform.Validators[0].(func(string) error)
// compositemodelrouteDescUpstreamModel is the schema descriptor for upstream_model field.
compositemodelrouteDescUpstreamModel := compositemodelrouteFields[4].Descriptor()
// compositemodelroute.DefaultUpstreamModel holds the default value on creation for the upstream_model field.
compositemodelroute.DefaultUpstreamModel = compositemodelrouteDescUpstreamModel.Default.(string)
// compositemodelroute.UpstreamModelValidator is a validator for the "upstream_model" field. It is called by the builders before save.
compositemodelroute.UpstreamModelValidator = compositemodelrouteDescUpstreamModel.Validators[0].(func(string) error)
// compositemodelrouteDescEndpoint is the schema descriptor for endpoint field.
compositemodelrouteDescEndpoint := compositemodelrouteFields[5].Descriptor()
// compositemodelroute.DefaultEndpoint holds the default value on creation for the endpoint field.
compositemodelroute.DefaultEndpoint = compositemodelrouteDescEndpoint.Default.(string)
// compositemodelroute.EndpointValidator is a validator for the "endpoint" field. It is called by the builders before save.
compositemodelroute.EndpointValidator = compositemodelrouteDescEndpoint.Validators[0].(func(string) error)
// compositemodelrouteDescPriority is the schema descriptor for priority field.
compositemodelrouteDescPriority := compositemodelrouteFields[6].Descriptor()
// compositemodelroute.DefaultPriority holds the default value on creation for the priority field.
compositemodelroute.DefaultPriority = compositemodelrouteDescPriority.Default.(int)
// compositemodelrouteDescEnabled is the schema descriptor for enabled field.
compositemodelrouteDescEnabled := compositemodelrouteFields[7].Descriptor()
// compositemodelroute.DefaultEnabled holds the default value on creation for the enabled field.
compositemodelroute.DefaultEnabled = compositemodelrouteDescEnabled.Default.(bool)
errorpassthroughruleMixin := schema.ErrorPassthroughRule{}.Mixin()
errorpassthroughruleMixinFields0 := errorpassthroughruleMixin[0].Fields()
_ = errorpassthroughruleMixinFields0
@@ -0,0 +1,87 @@
package schema
import (
"github.com/Wei-Shaw/sub2api/ent/schema/mixins"
"github.com/Wei-Shaw/sub2api/internal/domain"
"entgo.io/ent"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/edge"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// CompositeModelRoute holds model routing aliases for composite groups.
type CompositeModelRoute struct {
ent.Schema
}
func (CompositeModelRoute) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "composite_model_routes"},
}
}
func (CompositeModelRoute) Mixin() []ent.Mixin {
return []ent.Mixin{
mixins.TimeMixin{},
mixins.SoftDeleteMixin{},
}
}
func (CompositeModelRoute) Fields() []ent.Field {
return []ent.Field{
field.Int64("group_id"),
field.String("public_model").
MaxLen(200).
NotEmpty().
Comment("Client-facing model identifier or prefix."),
field.String("match_type").
MaxLen(20).
Default("exact").
Comment("exact or prefix."),
field.String("target_platform").
MaxLen(50).
Default(domain.PlatformOpenAI).
Comment("Concrete provider platform."),
field.String("upstream_model").
MaxLen(200).
Default("").
Comment("Provider model identifier; empty means public_model."),
field.String("endpoint").
MaxLen(50).
Default("any").
Comment("Endpoint scope such as any, messages, responses, chat_completions."),
field.Int("priority").
Default(100).
Comment("Lower values win within the same match strength."),
field.Bool("enabled").
Default(true),
field.String("notes").
Optional().
Nillable().
SchemaType(map[string]string{dialect.Postgres: "text"}),
}
}
func (CompositeModelRoute) Edges() []ent.Edge {
return []ent.Edge{
edge.To("group", Group.Type).
Unique().
Required().
Field("group_id"),
}
}
func (CompositeModelRoute) Indexes() []ent.Index {
return []ent.Index{
index.Fields("group_id"),
index.Fields("group_id", "enabled"),
index.Fields("group_id", "endpoint"),
index.Fields("group_id", "target_platform"),
index.Fields("deleted_at"),
index.Fields("priority"),
}
}
+3
View File
@@ -42,6 +42,8 @@ type Tx struct {
ChannelMonitorHistory *ChannelMonitorHistoryClient
// ChannelMonitorRequestTemplate is the client for interacting with the ChannelMonitorRequestTemplate builders.
ChannelMonitorRequestTemplate *ChannelMonitorRequestTemplateClient
// CompositeModelRoute is the client for interacting with the CompositeModelRoute builders.
CompositeModelRoute *CompositeModelRouteClient
// ErrorPassthroughRule is the client for interacting with the ErrorPassthroughRule builders.
ErrorPassthroughRule *ErrorPassthroughRuleClient
// Group is the client for interacting with the Group builders.
@@ -235,6 +237,7 @@ func (tx *Tx) init() {
tx.ChannelMonitorDailyRollup = NewChannelMonitorDailyRollupClient(tx.config)
tx.ChannelMonitorHistory = NewChannelMonitorHistoryClient(tx.config)
tx.ChannelMonitorRequestTemplate = NewChannelMonitorRequestTemplateClient(tx.config)
tx.CompositeModelRoute = NewCompositeModelRouteClient(tx.config)
tx.ErrorPassthroughRule = NewErrorPassthroughRuleClient(tx.config)
tx.Group = NewGroupClient(tx.config)
tx.IdempotencyRecord = NewIdempotencyRecordClient(tx.config)
@@ -34,6 +34,11 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
router.GET("/api/v1/admin/groups", groupHandler.List)
router.GET("/api/v1/admin/groups/all", groupHandler.GetAll)
router.GET("/api/v1/admin/groups/:id/models-list-candidates", groupHandler.GetModelsListCandidates)
router.GET("/api/v1/admin/groups/:id/composite-routes", groupHandler.ListCompositeRoutes)
router.POST("/api/v1/admin/groups/:id/composite-routes", groupHandler.CreateCompositeRoute)
router.POST("/api/v1/admin/groups/:id/composite-routes/preview", groupHandler.PreviewCompositeRoute)
router.PUT("/api/v1/admin/groups/:id/composite-routes/:route_id", groupHandler.UpdateCompositeRoute)
router.DELETE("/api/v1/admin/groups/:id/composite-routes/:route_id", groupHandler.DeleteCompositeRoute)
router.GET("/api/v1/admin/groups/:id", groupHandler.GetByID)
router.POST("/api/v1/admin/groups", groupHandler.Create)
router.PUT("/api/v1/admin/groups/:id", groupHandler.Update)
@@ -184,7 +189,54 @@ func TestGroupHandlerEndpoints(t *testing.T) {
require.Equal(t, http.StatusOK, rec.Code)
require.Contains(t, rec.Body.String(), "gpt-5.5")
body, _ := json.Marshal(map[string]any{"name": "new", "platform": "anthropic", "subscription_type": "standard"})
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodGet, "/api/v1/admin/groups/2/composite-routes", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Contains(t, rec.Body.String(), "openrouter/gpt-5")
body, _ := json.Marshal(map[string]any{
"public_model": "openrouter/gpt-5",
"match_type": "exact",
"target_platform": "openai",
"upstream_model": "gpt-5",
"endpoint": "chat_completions",
"enabled": true,
})
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/groups/2/composite-routes", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusCreated, rec.Code)
require.Contains(t, rec.Body.String(), "gpt-5")
body, _ = json.Marshal(map[string]any{
"public_model": "openrouter/gpt-5",
"target_platform": "openai",
"upstream_model": "gpt-5",
"endpoint": "responses",
"enabled": true,
})
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPut, "/api/v1/admin/groups/2/composite-routes/1", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
body, _ = json.Marshal(map[string]any{"model": "gpt-5", "endpoint": "responses"})
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/groups/2/composite-routes/preview", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Contains(t, rec.Body.String(), `"source":"detector"`)
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodDelete, "/api/v1/admin/groups/2/composite-routes/1", nil)
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
body, _ = json.Marshal(map[string]any{"name": "new", "platform": "anthropic", "subscription_type": "standard"})
rec = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/api/v1/admin/groups", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
@@ -293,6 +293,64 @@ func (s *stubAdminService) GetGroupModelsListCandidates(ctx context.Context, id
return []string{"claude-sonnet-4-6"}, nil
}
func (s *stubAdminService) ListCompositeRoutes(ctx context.Context, groupID int64) ([]service.CompositeModelRoute, error) {
return []service.CompositeModelRoute{
{
ID: 1,
GroupID: groupID,
PublicModel: "openrouter/gpt-5",
MatchType: service.CompositeRouteMatchExact,
TargetPlatform: service.PlatformOpenAI,
UpstreamModel: "gpt-5",
Endpoint: service.CompositeRouteEndpointAny,
Priority: 100,
Enabled: true,
},
}, nil
}
func (s *stubAdminService) CreateCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
return &service.CompositeModelRoute{
ID: 1,
GroupID: groupID,
PublicModel: input.PublicModel,
MatchType: input.MatchType,
TargetPlatform: input.TargetPlatform,
UpstreamModel: input.UpstreamModel,
Endpoint: input.Endpoint,
Priority: input.Priority,
Enabled: input.Enabled,
Notes: input.Notes,
}, nil
}
func (s *stubAdminService) UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
return &service.CompositeModelRoute{
ID: routeID,
GroupID: groupID,
PublicModel: input.PublicModel,
MatchType: input.MatchType,
TargetPlatform: input.TargetPlatform,
UpstreamModel: input.UpstreamModel,
Endpoint: input.Endpoint,
Priority: input.Priority,
Enabled: input.Enabled,
Notes: input.Notes,
}, nil
}
func (s *stubAdminService) DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error {
return nil
}
func (s *stubAdminService) PreviewCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRoutePreviewRequest) (*service.CompositeRouteDecision, error) {
decision, err := service.NewCompositeRouteResolver(nil).Resolve(ctx, groupID, input.Model, input.Endpoint)
if err != nil {
return nil, err
}
return &decision, nil
}
func (s *stubAdminService) CreateGroup(ctx context.Context, input *service.CreateGroupInput) (*service.Group, error) {
group := service.Group{ID: 200, Name: input.Name, Status: service.StatusActive}
return &group, nil
@@ -198,6 +198,22 @@ type UpdateGroupRequest struct {
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
type CompositeRouteRequest struct {
PublicModel string `json:"public_model" binding:"required"`
MatchType string `json:"match_type" binding:"omitempty,oneof=exact prefix"`
TargetPlatform string `json:"target_platform" binding:"required,oneof=anthropic openai gemini antigravity grok"`
UpstreamModel string `json:"upstream_model"`
Endpoint string `json:"endpoint" binding:"omitempty,oneof=any messages count_tokens responses chat_completions embeddings images gemini"`
Priority int `json:"priority"`
Enabled *bool `json:"enabled"`
Notes string `json:"notes"`
}
type CompositeRoutePreviewRequest struct {
Model string `json:"model" binding:"required"`
Endpoint string `json:"endpoint" binding:"omitempty,oneof=any messages count_tokens responses chat_completions embeddings images gemini"`
}
// List handles listing all groups with pagination
// GET /api/v1/admin/groups
func (h *GroupHandler) List(c *gin.Context) {
@@ -233,6 +249,133 @@ func (h *GroupHandler) List(c *gin.Context) {
response.Paginated(c, outGroups, total, page, pageSize)
}
// ListCompositeRoutes handles listing composite model routes for one group.
// GET /api/v1/admin/groups/:id/composite-routes
func (h *GroupHandler) ListCompositeRoutes(c *gin.Context) {
groupID, ok := parsePositiveIDParam(c, "id")
if !ok {
return
}
routes, err := h.adminService.ListCompositeRoutes(c.Request.Context(), groupID)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, routes)
}
// CreateCompositeRoute handles creating one composite model route.
// POST /api/v1/admin/groups/:id/composite-routes
func (h *GroupHandler) CreateCompositeRoute(c *gin.Context) {
groupID, ok := parsePositiveIDParam(c, "id")
if !ok {
return
}
var req CompositeRouteRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request body: "+err.Error())
return
}
route, err := h.adminService.CreateCompositeRoute(c.Request.Context(), groupID, compositeRouteRequestToInput(req, true))
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Created(c, route)
}
// UpdateCompositeRoute handles replacing one composite model route.
// PUT /api/v1/admin/groups/:id/composite-routes/:route_id
func (h *GroupHandler) UpdateCompositeRoute(c *gin.Context) {
groupID, ok := parsePositiveIDParam(c, "id")
if !ok {
return
}
routeID, ok := parsePositiveIDParam(c, "route_id")
if !ok {
return
}
var req CompositeRouteRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request body: "+err.Error())
return
}
route, err := h.adminService.UpdateCompositeRoute(c.Request.Context(), groupID, routeID, compositeRouteRequestToInput(req, true))
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, route)
}
// DeleteCompositeRoute handles deleting one composite model route.
// DELETE /api/v1/admin/groups/:id/composite-routes/:route_id
func (h *GroupHandler) DeleteCompositeRoute(c *gin.Context) {
groupID, ok := parsePositiveIDParam(c, "id")
if !ok {
return
}
routeID, ok := parsePositiveIDParam(c, "route_id")
if !ok {
return
}
if err := h.adminService.DeleteCompositeRoute(c.Request.Context(), groupID, routeID); err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, gin.H{"message": "Composite route deleted"})
}
// PreviewCompositeRoute resolves a model without mutating routes.
// POST /api/v1/admin/groups/:id/composite-routes/preview
func (h *GroupHandler) PreviewCompositeRoute(c *gin.Context) {
groupID, ok := parsePositiveIDParam(c, "id")
if !ok {
return
}
var req CompositeRoutePreviewRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request body: "+err.Error())
return
}
decision, err := h.adminService.PreviewCompositeRoute(c.Request.Context(), groupID, service.CompositeRoutePreviewRequest{
Model: req.Model,
Endpoint: req.Endpoint,
})
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, decision)
}
func compositeRouteRequestToInput(req CompositeRouteRequest, defaultEnabled bool) service.CompositeRouteInput {
enabled := defaultEnabled
if req.Enabled != nil {
enabled = *req.Enabled
}
return service.CompositeRouteInput{
PublicModel: req.PublicModel,
MatchType: req.MatchType,
TargetPlatform: req.TargetPlatform,
UpstreamModel: req.UpstreamModel,
Endpoint: req.Endpoint,
Priority: req.Priority,
Enabled: enabled,
Notes: req.Notes,
}
}
func parsePositiveIDParam(c *gin.Context, name string) (int64, bool) {
raw := c.Param(name)
id, err := strconv.ParseInt(raw, 10, 64)
if err != nil || id <= 0 {
response.BadRequest(c, "Invalid "+name)
return 0, false
}
return id, true
}
// GetAll handles getting all active groups without pagination.
// Pass ?include_inactive=true to also include disabled groups (used by the
// API Key group filter, which needs to surface groups that still have API keys
@@ -187,6 +187,7 @@ func newTestGatewayHandler(t *testing.T, group *service.Group, accounts []*servi
nil, // tlsFPProfileService
nil, // channelService
nil, // resolver
nil, // compositeResolver
nil, // balanceNotifyService
nil, // userPlatformQuotaRepo
)
@@ -56,7 +56,7 @@ func newGatewayModelsHandlerForTest(repo service.AccountRepository) *GatewayHand
gatewayService: service.NewGatewayService(
repo,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
),
}
}
@@ -98,6 +98,9 @@ func (h *GatewayHandler) GeminiV1BetaGetModel(c *gin.Context) {
googleError(c, http.StatusBadRequest, "Missing model in URL")
return
}
if resolvedModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context()); ok && strings.TrimSpace(resolvedModel) != "" {
modelName = strings.TrimSpace(resolvedModel)
}
// 强制 antigravity 模式:返回 antigravity 模型信息
if forcePlatform == service.PlatformAntigravity {
@@ -166,6 +169,9 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
googleError(c, http.StatusNotFound, err.Error())
return
}
if resolvedModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context()); ok && strings.TrimSpace(resolvedModel) != "" {
modelName = strings.TrimSpace(resolvedModel)
}
stream := action == "streamGenerateContent"
reqLog = reqLog.With(zap.String("model", modelName), zap.String("action", action), zap.Bool("stream", stream))
+1 -1
View File
@@ -1160,7 +1160,7 @@ func logOpsStreamError(c *gin.Context, ops *service.OpsService, wireStatus int)
}
fallbackPlatform := guessPlatformFromPath(c.Request.URL.Path)
platform := resolveOpsPlatform(apiKey, fallbackPlatform)
platform := resolveOpsPlatform(c.Request.Context(), apiKey, fallbackPlatform)
requestID := c.Writer.Header().Get("X-Request-Id")
if requestID == "" {
+9
View File
@@ -11,6 +11,15 @@ const (
// ResolvedTargetPlatform 是 composite 分组按请求模型解析出的真实目标平台。
ResolvedTargetPlatform Key = "ctx_resolved_target_platform"
// ResolvedUpstreamModel 是 composite 分组将公开模型名解析到的上游模型名。
ResolvedUpstreamModel Key = "ctx_resolved_upstream_model"
// RequestedPublicModel 是客户端原始请求中的公开模型名。
RequestedPublicModel Key = "ctx_requested_public_model"
// CompositeRouteSource 标识 composite 解析结果来自显式路由还是内置模型探测。
CompositeRouteSource Key = "ctx_composite_route_source"
// RequestID 为服务端生成/透传的请求 ID。
RequestID Key = "ctx_request_id"
@@ -0,0 +1,113 @@
package repository
import (
"context"
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/compositemodelroute"
"github.com/Wei-Shaw/sub2api/internal/service"
)
type compositeModelRouteRepository struct {
client *dbent.Client
}
func NewCompositeModelRouteRepository(client *dbent.Client) service.CompositeModelRouteRepository {
return &compositeModelRouteRepository{client: client}
}
func (r *compositeModelRouteRepository) ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]service.CompositeModelRoute, error) {
q := clientFromContext(ctx, r.client).CompositeModelRoute.Query().
Where(compositemodelroute.GroupIDEQ(groupID)).
Order(
dbent.Asc(compositemodelroute.FieldPriority),
dbent.Asc(compositemodelroute.FieldID),
)
if !includeDisabled {
q = q.Where(compositemodelroute.EnabledEQ(true))
}
rows, err := q.All(ctx)
if err != nil {
return nil, err
}
out := make([]service.CompositeModelRoute, 0, len(rows))
for _, row := range rows {
out = append(out, *compositeModelRouteEntityToService(row))
}
return out, nil
}
func (r *compositeModelRouteRepository) Create(ctx context.Context, route *service.CompositeModelRoute) error {
if route == nil {
return service.ErrCompositeRouteNotFound
}
created, err := clientFromContext(ctx, r.client).CompositeModelRoute.Create().
SetGroupID(route.GroupID).
SetPublicModel(route.PublicModel).
SetMatchType(route.MatchType).
SetTargetPlatform(route.TargetPlatform).
SetUpstreamModel(route.UpstreamModel).
SetEndpoint(route.Endpoint).
SetPriority(route.Priority).
SetEnabled(route.Enabled).
SetNotes(route.Notes).
Save(ctx)
if err != nil {
return translatePersistenceError(err, nil, service.ErrCompositeRouteExists)
}
*route = *compositeModelRouteEntityToService(created)
return nil
}
func (r *compositeModelRouteRepository) Update(ctx context.Context, route *service.CompositeModelRoute) error {
if route == nil {
return service.ErrCompositeRouteNotFound
}
updated, err := clientFromContext(ctx, r.client).CompositeModelRoute.UpdateOneID(route.ID).
SetPublicModel(route.PublicModel).
SetMatchType(route.MatchType).
SetTargetPlatform(route.TargetPlatform).
SetUpstreamModel(route.UpstreamModel).
SetEndpoint(route.Endpoint).
SetPriority(route.Priority).
SetEnabled(route.Enabled).
SetNotes(route.Notes).
Save(ctx)
if err != nil {
return translatePersistenceError(err, service.ErrCompositeRouteNotFound, service.ErrCompositeRouteExists)
}
*route = *compositeModelRouteEntityToService(updated)
return nil
}
func (r *compositeModelRouteRepository) Delete(ctx context.Context, id int64) error {
err := clientFromContext(ctx, r.client).CompositeModelRoute.DeleteOneID(id).Exec(ctx)
return translatePersistenceError(err, service.ErrCompositeRouteNotFound, nil)
}
func (r *compositeModelRouteRepository) DeleteByGroup(ctx context.Context, groupID int64) error {
_, err := clientFromContext(ctx, r.client).CompositeModelRoute.Delete().
Where(compositemodelroute.GroupIDEQ(groupID)).
Exec(ctx)
return err
}
func compositeModelRouteEntityToService(row *dbent.CompositeModelRoute) *service.CompositeModelRoute {
if row == nil {
return nil
}
return &service.CompositeModelRoute{
ID: row.ID,
GroupID: row.GroupID,
PublicModel: row.PublicModel,
MatchType: row.MatchType,
TargetPlatform: row.TargetPlatform,
UpstreamModel: row.UpstreamModel,
Endpoint: row.Endpoint,
Priority: row.Priority,
Enabled: row.Enabled,
Notes: derefString(row.Notes),
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
+6 -1
View File
@@ -835,7 +835,12 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64,
return nil, err
}
// 4. Soft-delete group itself.
// 4. Soft-delete composite model routes owned by this group.
if _, err := exec.ExecContext(ctx, "UPDATE composite_model_routes SET deleted_at = NOW() WHERE group_id = $1 AND deleted_at IS NULL", id); err != nil {
return nil, err
}
// 5. Soft-delete group itself.
if _, err := txClient.Group.Delete().Where(group.IDEQ(id)).Exec(ctx); err != nil {
return nil, err
}
+1
View File
@@ -69,6 +69,7 @@ var ProviderSet = wire.NewSet(
NewAPIKeyRepository,
NewGroupRepository,
NewAdminGroupRepository,
NewCompositeModelRouteRepository,
NewAccountRepository,
NewAdminAccountRepository,
NewScheduledTestPlanRepository, // 定时测试计划仓储
+1 -1
View File
@@ -1396,7 +1396,7 @@ func newContractDeps(t *testing.T) *contractDeps {
settingRepo := newStubSettingRepo()
settingService := service.NewSettingService(settingRepo, cfg)
adminService := service.NewAdminService(userRepo, groupRepo, &accountRepo, proxyRepo, apiKeyRepo, redeemRepo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
adminService := service.NewAdminService(userRepo, groupRepo, &accountRepo, proxyRepo, apiKeyRepo, redeemRepo, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
authHandler := handler.NewAuthHandler(cfg, nil, userService, settingService, nil, redeemService, nil, nil)
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
usageHandler := handler.NewUsageHandler(usageService, apiKeyService, nil, nil)
+2 -1
View File
@@ -39,6 +39,7 @@ func ProvideRouter(
subscriptionService *service.SubscriptionService,
opsService *service.OpsService,
settingService *service.SettingService,
compositeResolver *service.CompositeRouteResolver,
redisClient *redis.Client,
) *gin.Engine {
if cfg.Server.Mode == "release" {
@@ -85,7 +86,7 @@ func ProvideRouter(
service.SetWebSearchManager(websearch.NewManager(configs, redisClient))
})
return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, auditLog, stepUpAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient)
return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, auditLog, stepUpAuth, apiKeyService, subscriptionService, opsService, settingService, compositeResolver, cfg, redisClient)
}
func configureTrustedProxies(r *gin.Engine, cfg config.ServerConfig) {
+4 -2
View File
@@ -32,6 +32,7 @@ func SetupRouter(
subscriptionService *service.SubscriptionService,
opsService *service.OpsService,
settingService *service.SettingService,
compositeResolver *service.CompositeRouteResolver,
cfg *config.Config,
redisClient *redis.Client,
) *gin.Engine {
@@ -88,7 +89,7 @@ func SetupRouter(
}
// 注册路由
registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, auditLog, stepUpAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient)
registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, auditLog, stepUpAuth, apiKeyService, subscriptionService, opsService, settingService, compositeResolver, cfg, redisClient)
return r
}
@@ -106,6 +107,7 @@ func registerRoutes(
subscriptionService *service.SubscriptionService,
opsService *service.OpsService,
settingService *service.SettingService,
compositeResolver *service.CompositeRouteResolver,
cfg *config.Config,
redisClient *redis.Client,
) {
@@ -119,7 +121,7 @@ func registerRoutes(
routes.RegisterAuthRoutes(v1, h, jwtAuth, auditLog, redisClient, settingService)
routes.RegisterUserRoutes(v1, h, jwtAuth, auditLog, settingService)
routes.RegisterAdminRoutes(v1, h, adminAuth, auditLog, stepUpAuth, settingService)
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg)
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, compositeResolver, cfg)
routes.RegisterPaymentRoutes(v1, h.Payment, h.PaymentWebhook, h.Admin.Payment, jwtAuth, adminAuth, auditLog, settingService)
handler.RegisterPageRoutes(v1, cfg.Pricing.DataDir, gin.HandlerFunc(jwtAuth), gin.HandlerFunc(adminAuth), settingService)
+5
View File
@@ -319,6 +319,11 @@ func registerGroupRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
groups.GET("/capacity-summary", h.Admin.Group.GetCapacitySummary)
groups.PUT("/sort-order", h.Admin.Group.UpdateSortOrder)
groups.GET("/:id/models-list-candidates", h.Admin.Group.GetModelsListCandidates)
groups.GET("/:id/composite-routes", h.Admin.Group.ListCompositeRoutes)
groups.POST("/:id/composite-routes", h.Admin.Group.CreateCompositeRoute)
groups.POST("/:id/composite-routes/preview", h.Admin.Group.PreviewCompositeRoute)
groups.PUT("/:id/composite-routes/:route_id", h.Admin.Group.UpdateCompositeRoute)
groups.DELETE("/:id/composite-routes/:route_id", h.Admin.Group.DeleteCompositeRoute)
groups.GET("/:id", h.Admin.Group.GetByID)
groups.POST("", h.Admin.Group.Create)
groups.POST("/:id/duplicate", h.Admin.Group.Duplicate)
@@ -1,6 +1,7 @@
package routes
import (
"context"
"io"
"net/http"
"net/http/httptest"
@@ -13,6 +14,40 @@ import (
"github.com/stretchr/testify/require"
)
type compositeRouteRepoStub struct {
routes []service.CompositeModelRoute
}
func (s compositeRouteRepoStub) ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]service.CompositeModelRoute, error) {
routes := make([]service.CompositeModelRoute, 0, len(s.routes))
for _, route := range s.routes {
if route.GroupID != groupID {
continue
}
if !includeDisabled && !route.Enabled {
continue
}
routes = append(routes, route)
}
return routes, nil
}
func (s compositeRouteRepoStub) Create(ctx context.Context, route *service.CompositeModelRoute) error {
return nil
}
func (s compositeRouteRepoStub) Update(ctx context.Context, route *service.CompositeModelRoute) error {
return nil
}
func (s compositeRouteRepoStub) Delete(ctx context.Context, id int64) error {
return nil
}
func (s compositeRouteRepoStub) DeleteByGroup(ctx context.Context, groupID int64) error {
return nil
}
func TestCompositeTargetPlatformMiddlewareResolvesModelAndRestoresBody(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
@@ -24,7 +59,7 @@ func TestCompositeTargetPlatformMiddlewareResolvesModelAndRestoresBody(t *testin
})
c.Next()
})))
router.Use(compositeTargetPlatformMiddleware())
router.Use(compositeTargetPlatformMiddleware(nil))
router.POST("/", func(c *gin.Context) {
platform, ok := service.ResolvedTargetPlatformFromContext(c.Request.Context())
require.True(t, ok)
@@ -44,3 +79,101 @@ func TestCompositeTargetPlatformMiddlewareResolvesModelAndRestoresBody(t *testin
require.Equal(t, http.StatusNoContent, w.Code)
}
func TestCompositeTargetPlatformMiddlewareUsesExplicitRouteAndRewritesBody(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
resolver := service.NewCompositeRouteResolver(compositeRouteRepoStub{
routes: []service.CompositeModelRoute{
{
ID: 1,
GroupID: 1,
PublicModel: "openrouter/gpt-5",
MatchType: service.CompositeRouteMatchExact,
TargetPlatform: service.PlatformOpenAI,
UpstreamModel: "gpt-5",
Endpoint: service.CompositeRouteEndpointAny,
Priority: 100,
Enabled: true,
},
},
})
router.Use(gin.HandlerFunc(servermiddleware.APIKeyAuthMiddleware(func(c *gin.Context) {
groupID := int64(1)
c.Set(string(servermiddleware.ContextKeyAPIKey), &service.APIKey{
GroupID: &groupID,
Group: &service.Group{ID: groupID, Platform: service.PlatformComposite},
})
c.Next()
})))
router.Use(compositeTargetPlatformMiddleware(resolver))
router.POST("/v1/chat/completions", func(c *gin.Context) {
platform, ok := service.ResolvedTargetPlatformFromContext(c.Request.Context())
require.True(t, ok)
require.Equal(t, service.PlatformOpenAI, platform)
upstreamModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context())
require.True(t, ok)
require.Equal(t, "gpt-5", upstreamModel)
body, err := io.ReadAll(c.Request.Body)
require.NoError(t, err)
require.JSONEq(t, `{"model":"gpt-5","messages":[]}`, string(body))
c.Status(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"openrouter/gpt-5","messages":[]}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusNoContent, w.Code)
}
func TestCompositeGeminiTargetPlatformMiddlewareUsesPathRoute(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
resolver := service.NewCompositeRouteResolver(compositeRouteRepoStub{
routes: []service.CompositeModelRoute{
{
ID: 1,
GroupID: 1,
PublicModel: "openrouter/gemini-pro",
MatchType: service.CompositeRouteMatchExact,
TargetPlatform: service.PlatformGemini,
UpstreamModel: "gemini-2.5-pro",
Endpoint: service.CompositeRouteEndpointGemini,
Priority: 100,
Enabled: true,
},
},
})
router.Use(gin.HandlerFunc(servermiddleware.APIKeyAuthMiddleware(func(c *gin.Context) {
groupID := int64(1)
c.Set(string(servermiddleware.ContextKeyAPIKey), &service.APIKey{
GroupID: &groupID,
Group: &service.Group{ID: groupID, Platform: service.PlatformComposite},
})
c.Next()
})))
router.Use(compositeGeminiTargetPlatformMiddleware(resolver))
router.POST("/v1beta/models/*modelAction", func(c *gin.Context) {
platform, ok := service.ResolvedTargetPlatformFromContext(c.Request.Context())
require.True(t, ok)
require.Equal(t, service.PlatformGemini, platform)
upstreamModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context())
require.True(t, ok)
require.Equal(t, "gemini-2.5-pro", upstreamModel)
c.Status(http.StatusNoContent)
})
req := httptest.NewRequest(http.MethodPost, "/v1beta/models/openrouter/gemini-pro:generateContent", strings.NewReader(`{"contents":[]}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusNoContent, w.Code)
}
+84 -13
View File
@@ -16,6 +16,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// RegisterGatewayRoutes 注册 API 网关路由(Claude/OpenAI/Gemini 兼容)
@@ -27,6 +28,7 @@ func RegisterGatewayRoutes(
subscriptionService *service.SubscriptionService,
opsService *service.OpsService,
settingService *service.SettingService,
compositeResolver *service.CompositeRouteResolver,
cfg *config.Config,
) {
bodyLimit := middleware.RequestBodyLimit(cfg.Gateway.MaxBodySize)
@@ -34,8 +36,8 @@ func RegisterGatewayRoutes(
clientRequestID := middleware.ClientRequestID()
opsErrorLogger := handler.OpsErrorLoggerMiddleware(opsService)
endpointNorm := handler.InboundEndpointMiddleware()
compositeTarget := compositeTargetPlatformMiddleware()
compositeGeminiTarget := compositeImplicitTargetPlatformMiddleware(service.PlatformGemini)
compositeTarget := compositeTargetPlatformMiddleware(compositeResolver)
compositeGeminiTarget := compositeGeminiTargetPlatformMiddleware(compositeResolver)
// 未分组 Key 拦截中间件(按协议格式区分错误响应)
requireGroupAnthropic := middleware.RequireGroupAssignment(settingService, middleware.AnthropicErrorWriter)
@@ -356,7 +358,10 @@ func getGroupPlatform(c *gin.Context) string {
return apiKey.Group.Platform
}
func compositeTargetPlatformMiddleware() gin.HandlerFunc {
func compositeTargetPlatformMiddleware(resolver *service.CompositeRouteResolver) gin.HandlerFunc {
if resolver == nil {
resolver = service.NewCompositeRouteResolver(nil)
}
return func(c *gin.Context) {
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if !ok || apiKey == nil || apiKey.Group == nil || apiKey.Group.Platform != service.PlatformComposite {
@@ -381,26 +386,71 @@ func compositeTargetPlatformMiddleware() gin.HandlerFunc {
c.Abort()
return
}
resetRequestBody(c, body)
model := strings.TrimSpace(gjson.GetBytes(body, "model").String())
if model != "" {
if platform, ok := service.DetectModelPlatform(model); ok {
c.Request = c.Request.WithContext(service.WithResolvedTargetPlatform(c.Request.Context(), platform))
decision, err := resolver.Resolve(c.Request.Context(), apiKey.Group.ID, model, compositeRouteEndpointForPath(c.Request.URL.Path))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"type": "server_error", "message": "Failed to resolve composite model route"}})
c.Abort()
return
}
if decision.Matched {
c.Request = c.Request.WithContext(service.WithCompositeRouteDecision(c.Request.Context(), decision))
if upstreamModel := strings.TrimSpace(decision.UpstreamModel); upstreamModel != "" && upstreamModel != model && gjson.ValidBytes(body) {
if rewritten, rewriteErr := sjson.SetBytes(body, "model", upstreamModel); rewriteErr == nil {
body = rewritten
}
}
}
}
resetRequestBody(c, body)
c.Next()
}
}
func compositeGeminiTargetPlatformMiddleware(resolver *service.CompositeRouteResolver) gin.HandlerFunc {
if resolver == nil {
resolver = service.NewCompositeRouteResolver(nil)
}
return func(c *gin.Context) {
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if ok && apiKey != nil && apiKey.Group != nil && apiKey.Group.Platform == service.PlatformComposite {
model := compositeGeminiModelFromParams(c)
if model != "" {
decision, err := resolver.Resolve(c.Request.Context(), apiKey.Group.ID, model, service.CompositeRouteEndpointGemini)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"type": "server_error", "message": "Failed to resolve composite model route"}})
c.Abort()
return
}
if decision.Matched {
c.Request = c.Request.WithContext(service.WithCompositeRouteDecision(c.Request.Context(), decision))
}
}
if _, resolved := service.ResolvedTargetPlatformFromContext(c.Request.Context()); !resolved {
c.Request = c.Request.WithContext(service.WithResolvedTargetPlatform(c.Request.Context(), service.PlatformGemini))
}
}
c.Next()
}
}
func compositeImplicitTargetPlatformMiddleware(platform string) gin.HandlerFunc {
return func(c *gin.Context) {
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if ok && apiKey != nil && apiKey.Group != nil && apiKey.Group.Platform == service.PlatformComposite {
c.Request = c.Request.WithContext(service.WithResolvedTargetPlatform(c.Request.Context(), platform))
}
c.Next()
func compositeGeminiModelFromParams(c *gin.Context) string {
if c == nil {
return ""
}
if model := strings.TrimSpace(c.Param("model")); model != "" {
return model
}
modelAction := strings.TrimPrefix(strings.TrimSpace(c.Param("modelAction")), "/")
if modelAction == "" {
return ""
}
if idx := strings.LastIndex(modelAction, ":"); idx >= 0 {
return strings.TrimSpace(modelAction[:idx])
}
return modelAction
}
func resetRequestBody(c *gin.Context, body []byte) {
@@ -408,3 +458,24 @@ func resetRequestBody(c *gin.Context, body []byte) {
c.Request.ContentLength = int64(len(body))
c.Request.Header.Set("Content-Length", strconv.Itoa(len(body)))
}
func compositeRouteEndpointForPath(path string) string {
switch {
case strings.Contains(path, "/messages/count_tokens"):
return service.CompositeRouteEndpointCountTokens
case strings.Contains(path, "/messages"):
return service.CompositeRouteEndpointMessages
case strings.Contains(path, "/responses"):
return service.CompositeRouteEndpointResponses
case strings.Contains(path, "/chat/completions"):
return service.CompositeRouteEndpointChatCompletions
case strings.Contains(path, "/embeddings"):
return service.CompositeRouteEndpointEmbeddings
case strings.Contains(path, "/images/"):
return service.CompositeRouteEndpointImages
case strings.Contains(path, "/v1beta/"):
return service.CompositeRouteEndpointGemini
default:
return service.CompositeRouteEndpointAny
}
}
+19 -1
View File
@@ -27,7 +27,6 @@ func newGatewayRoutesTestRouterWithConfig(cfg *config.Config, platform ...string
if len(platform) > 0 && platform[0] != "" {
groupPlatform = platform[0]
}
RegisterGatewayRoutes(
router,
&handler.Handlers{
@@ -47,6 +46,7 @@ func newGatewayRoutesTestRouterWithConfig(cfg *config.Config, platform ...string
nil,
nil,
nil,
nil,
cfg,
)
@@ -206,6 +206,24 @@ func TestGatewayRoutesNonGrokVideosAreRejectedAtPlatformGate(t *testing.T) {
}
}
func TestGatewayRoutesCompositeOpenAIOnlyEndpointsRequireOpenAITarget(t *testing.T) {
router := newGatewayRoutesTestRouter(service.PlatformComposite)
req := httptest.NewRequest(http.MethodPost, "/v1/embeddings", strings.NewReader(`{"model":"gemini-2.5-pro","input":"hello"}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
require.Equal(t, http.StatusNotFound, w.Code)
req = httptest.NewRequest(http.MethodPost, "/v1/embeddings", strings.NewReader(`{"model":"text-embedding-3-small","input":"hello"}`))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
require.NotEqual(t, http.StatusNotFound, w.Code)
}
func TestGatewayRoutesGrokAllowsCLICompatibilityEntrypoints(t *testing.T) {
router := newGatewayRoutesTestRouter(service.PlatformGrok)
+128
View File
@@ -100,6 +100,134 @@ func (s *adminServiceImpl) GetGroupModelsListCandidates(ctx context.Context, id
return candidates, nil
}
func (s *adminServiceImpl) ListCompositeRoutes(ctx context.Context, groupID int64) ([]CompositeModelRoute, error) {
if err := s.requireCompositeGroup(ctx, groupID); err != nil {
return nil, err
}
if s.compositeRouteRepo == nil {
return nil, fmt.Errorf("composite route repository is not configured")
}
return s.compositeRouteRepo.ListByGroup(ctx, groupID, true)
}
func (s *adminServiceImpl) CreateCompositeRoute(ctx context.Context, groupID int64, input CompositeRouteInput) (*CompositeModelRoute, error) {
if err := s.requireCompositeGroup(ctx, groupID); err != nil {
return nil, err
}
if s.compositeRouteRepo == nil {
return nil, fmt.Errorf("composite route repository is not configured")
}
route, err := compositeRouteFromInput(groupID, input)
if err != nil {
return nil, err
}
if err := s.compositeRouteRepo.Create(ctx, route); err != nil {
return nil, err
}
return route, nil
}
func (s *adminServiceImpl) UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input CompositeRouteInput) (*CompositeModelRoute, error) {
if err := s.requireCompositeGroup(ctx, groupID); err != nil {
return nil, err
}
if s.compositeRouteRepo == nil {
return nil, fmt.Errorf("composite route repository is not configured")
}
if ok, err := s.compositeRouteBelongsToGroup(ctx, groupID, routeID); err != nil {
return nil, err
} else if !ok {
return nil, ErrCompositeRouteNotFound
}
route, err := compositeRouteFromInput(groupID, input)
if err != nil {
return nil, err
}
route.ID = routeID
if err := s.compositeRouteRepo.Update(ctx, route); err != nil {
return nil, err
}
return route, nil
}
func (s *adminServiceImpl) DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error {
if err := s.requireCompositeGroup(ctx, groupID); err != nil {
return err
}
if s.compositeRouteRepo == nil {
return fmt.Errorf("composite route repository is not configured")
}
if ok, err := s.compositeRouteBelongsToGroup(ctx, groupID, routeID); err != nil {
return err
} else if !ok {
return ErrCompositeRouteNotFound
}
return s.compositeRouteRepo.Delete(ctx, routeID)
}
func (s *adminServiceImpl) PreviewCompositeRoute(ctx context.Context, groupID int64, input CompositeRoutePreviewRequest) (*CompositeRouteDecision, error) {
if err := s.requireCompositeGroup(ctx, groupID); err != nil {
return nil, err
}
resolver := s.compositeResolver
if resolver == nil {
resolver = NewCompositeRouteResolver(s.compositeRouteRepo)
}
decision, err := resolver.Resolve(ctx, groupID, input.Model, input.Endpoint)
if err != nil {
return nil, err
}
return &decision, nil
}
func (s *adminServiceImpl) requireCompositeGroup(ctx context.Context, groupID int64) error {
group, err := s.groupRepo.GetByIDLite(ctx, groupID)
if err != nil {
return err
}
if group.Platform != PlatformComposite {
return fmt.Errorf("group %d is not a composite group", groupID)
}
return nil
}
func (s *adminServiceImpl) compositeRouteBelongsToGroup(ctx context.Context, groupID, routeID int64) (bool, error) {
routes, err := s.compositeRouteRepo.ListByGroup(ctx, groupID, true)
if err != nil {
return false, err
}
for i := range routes {
if routes[i].ID == routeID {
return true, nil
}
}
return false, nil
}
func compositeRouteFromInput(groupID int64, input CompositeRouteInput) (*CompositeModelRoute, error) {
input = normalizeCompositeRouteInput(input)
if input.PublicModel == "" {
return nil, fmt.Errorf("public_model is required")
}
if !isConcreteRequestPlatform(input.TargetPlatform) {
return nil, fmt.Errorf("target_platform must be a concrete provider")
}
if input.Priority == 0 {
input.Priority = 100
}
return &CompositeModelRoute{
GroupID: groupID,
PublicModel: input.PublicModel,
MatchType: input.MatchType,
TargetPlatform: input.TargetPlatform,
UpstreamModel: input.UpstreamModel,
Endpoint: input.Endpoint,
Priority: input.Priority,
Enabled: input.Enabled,
Notes: input.Notes,
}, nil
}
func defaultModelsListCandidateIDs(platform string) []string {
switch platform {
case PlatformOpenAI:
+11
View File
@@ -48,6 +48,11 @@ type AdminService interface {
RecoverDuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*Group, error)
UpdateGroup(ctx context.Context, id int64, input *UpdateGroupInput) (*Group, error)
DeleteGroup(ctx context.Context, id int64) error
ListCompositeRoutes(ctx context.Context, groupID int64) ([]CompositeModelRoute, error)
CreateCompositeRoute(ctx context.Context, groupID int64, input CompositeRouteInput) (*CompositeModelRoute, error)
UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input CompositeRouteInput) (*CompositeModelRoute, error)
DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error
PreviewCompositeRoute(ctx context.Context, groupID int64, input CompositeRoutePreviewRequest) (*CompositeRouteDecision, error)
GetGroupAPIKeys(ctx context.Context, groupID int64, page, pageSize int) ([]APIKey, int64, error)
GetGroupRateMultipliers(ctx context.Context, groupID int64) ([]UserGroupRateEntry, error)
ClearGroupRateMultipliers(ctx context.Context, groupID int64) error
@@ -626,6 +631,8 @@ type adminServiceImpl struct {
privacyClientFactory PrivacyClientFactory
runtimeBlocker AccountRuntimeBlocker
affiliateService adminRechargeAffiliateAccruer
compositeRouteRepo CompositeModelRouteRepository
compositeResolver *CompositeRouteResolver
}
type adminRechargeAffiliateAccruer interface {
@@ -657,6 +664,8 @@ func NewAdminService(
privacyClientFactory PrivacyClientFactory,
runtimeBlocker AccountRuntimeBlocker,
affiliateService *AffiliateService,
compositeRouteRepo CompositeModelRouteRepository,
compositeResolver *CompositeRouteResolver,
) AdminService {
return &adminServiceImpl{
userRepo: userRepo,
@@ -680,5 +689,7 @@ func NewAdminService(
privacyClientFactory: privacyClientFactory,
runtimeBlocker: runtimeBlocker,
affiliateService: affiliateService,
compositeRouteRepo: compositeRouteRepo,
compositeResolver: compositeResolver,
}
}
@@ -156,6 +156,83 @@ func (s *groupRepoStubForAdmin) UpdateSortOrders(_ context.Context, _ []GroupSor
return nil
}
type compositeRouteRepoStubForAdmin struct {
routes []CompositeModelRoute
created *CompositeModelRoute
updated *CompositeModelRoute
deleted []int64
nextID int64
listErr error
createErr error
updateErr error
deleteErr error
}
func (s *compositeRouteRepoStubForAdmin) ListByGroup(_ context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error) {
if s.listErr != nil {
return nil, s.listErr
}
routes := make([]CompositeModelRoute, 0, len(s.routes))
for _, route := range s.routes {
if route.GroupID != groupID {
continue
}
if !includeDisabled && !route.Enabled {
continue
}
routes = append(routes, route)
}
return routes, nil
}
func (s *compositeRouteRepoStubForAdmin) Create(_ context.Context, route *CompositeModelRoute) error {
if s.createErr != nil {
return s.createErr
}
if s.nextID > 0 {
route.ID = s.nextID
}
cloned := *route
s.created = &cloned
s.routes = append(s.routes, cloned)
return nil
}
func (s *compositeRouteRepoStubForAdmin) Update(_ context.Context, route *CompositeModelRoute) error {
if s.updateErr != nil {
return s.updateErr
}
cloned := *route
s.updated = &cloned
for i := range s.routes {
if s.routes[i].ID == route.ID {
s.routes[i] = cloned
return nil
}
}
s.routes = append(s.routes, cloned)
return nil
}
func (s *compositeRouteRepoStubForAdmin) Delete(_ context.Context, id int64) error {
if s.deleteErr != nil {
return s.deleteErr
}
s.deleted = append(s.deleted, id)
return nil
}
func (s *compositeRouteRepoStubForAdmin) DeleteByGroup(_ context.Context, groupID int64) error {
next := s.routes[:0]
for _, route := range s.routes {
if route.GroupID != groupID {
next = append(next, route)
}
}
s.routes = next
return nil
}
func TestAdminService_ListGroups_PassesSortParams(t *testing.T) {
repo := &groupRepoStubForAdmin{
listWithFiltersGroups: []Group{{ID: 1, Name: "g1"}},
@@ -1466,3 +1543,122 @@ func TestAdminService_UpdateGroup_InvalidRequestFallbackAllowsAntigravity(t *tes
require.NotNil(t, repo.updated)
require.Equal(t, fallbackID, *repo.updated.FallbackGroupIDOnInvalidRequest)
}
func TestAdminService_CreateCompositeRoute_RejectsNonCompositeGroup(t *testing.T) {
groupRepo := &groupRepoStubForAdmin{
getByID: &Group{ID: 7, Platform: PlatformOpenAI},
}
routeRepo := &compositeRouteRepoStubForAdmin{}
svc := &adminServiceImpl{groupRepo: groupRepo, compositeRouteRepo: routeRepo}
_, err := svc.CreateCompositeRoute(context.Background(), 7, CompositeRouteInput{
PublicModel: "router/gpt-5",
TargetPlatform: PlatformOpenAI,
Enabled: true,
})
require.Error(t, err)
require.ErrorContains(t, err, "not a composite group")
require.Nil(t, routeRepo.created)
}
func TestAdminService_CreateCompositeRoute_NormalizesAndPersists(t *testing.T) {
groupRepo := &groupRepoStubForAdmin{
getByID: &Group{ID: 7, Platform: PlatformComposite},
}
routeRepo := &compositeRouteRepoStubForAdmin{nextID: 99}
svc := &adminServiceImpl{groupRepo: groupRepo, compositeRouteRepo: routeRepo}
route, err := svc.CreateCompositeRoute(context.Background(), 7, CompositeRouteInput{
PublicModel: " router/gpt- ",
MatchType: CompositeRouteMatchPrefix,
TargetPlatform: PlatformOpenAI,
Endpoint: CompositeRouteEndpointResponses,
Enabled: true,
Notes: " route note ",
})
require.NoError(t, err)
require.NotNil(t, route)
require.Equal(t, int64(99), route.ID)
require.Equal(t, "router/gpt-", route.PublicModel)
require.Equal(t, CompositeRouteMatchPrefix, route.MatchType)
require.Equal(t, PlatformOpenAI, route.TargetPlatform)
require.Equal(t, "router/gpt-", route.UpstreamModel)
require.Equal(t, CompositeRouteEndpointResponses, route.Endpoint)
require.Equal(t, 100, route.Priority)
require.True(t, route.Enabled)
require.Equal(t, "route note", route.Notes)
require.Equal(t, route, routeRepo.created)
}
func TestAdminService_UpdateAndDeleteCompositeRouteRequireRouteOwnership(t *testing.T) {
groupRepo := &groupRepoStubForAdmin{
getByID: &Group{ID: 7, Platform: PlatformComposite},
}
routeRepo := &compositeRouteRepoStubForAdmin{
routes: []CompositeModelRoute{
{ID: 11, GroupID: 7, PublicModel: "router/gpt-5", TargetPlatform: PlatformOpenAI, Enabled: true},
{ID: 12, GroupID: 8, PublicModel: "router/other", TargetPlatform: PlatformGemini, Enabled: true},
},
}
svc := &adminServiceImpl{groupRepo: groupRepo, compositeRouteRepo: routeRepo}
updated, err := svc.UpdateCompositeRoute(context.Background(), 7, 11, CompositeRouteInput{
PublicModel: "router/gpt-5",
TargetPlatform: PlatformGemini,
UpstreamModel: "gemini-2.5-pro",
Endpoint: CompositeRouteEndpointChatCompletions,
Priority: 3,
Enabled: true,
})
require.NoError(t, err)
require.Equal(t, int64(11), updated.ID)
require.Equal(t, PlatformGemini, updated.TargetPlatform)
require.Equal(t, "gemini-2.5-pro", updated.UpstreamModel)
require.Equal(t, updated, routeRepo.updated)
err = svc.DeleteCompositeRoute(context.Background(), 7, 12)
require.ErrorIs(t, err, ErrCompositeRouteNotFound)
require.Empty(t, routeRepo.deleted)
err = svc.DeleteCompositeRoute(context.Background(), 7, 11)
require.NoError(t, err)
require.Equal(t, []int64{11}, routeRepo.deleted)
}
func TestAdminService_PreviewCompositeRouteUsesExplicitRoutes(t *testing.T) {
groupRepo := &groupRepoStubForAdmin{
getByID: &Group{ID: 7, Platform: PlatformComposite},
}
routeRepo := &compositeRouteRepoStubForAdmin{
routes: []CompositeModelRoute{
{
ID: 11,
GroupID: 7,
PublicModel: "openrouter/claude",
MatchType: CompositeRouteMatchExact,
TargetPlatform: PlatformAnthropic,
UpstreamModel: "claude-sonnet-4-6",
Endpoint: CompositeRouteEndpointMessages,
Priority: 100,
Enabled: true,
},
},
}
svc := &adminServiceImpl{groupRepo: groupRepo, compositeRouteRepo: routeRepo}
decision, err := svc.PreviewCompositeRoute(context.Background(), 7, CompositeRoutePreviewRequest{
Model: "openrouter/claude",
Endpoint: CompositeRouteEndpointMessages,
})
require.NoError(t, err)
require.NotNil(t, decision)
require.True(t, decision.Matched)
require.Equal(t, CompositeRouteSourceExplicit, decision.Source)
require.Equal(t, PlatformAnthropic, decision.TargetPlatform)
require.Equal(t, "claude-sonnet-4-6", decision.UpstreamModel)
require.NotNil(t, decision.Route)
require.Equal(t, int64(11), decision.Route.ID)
}
@@ -0,0 +1,126 @@
package service
import (
"context"
"strings"
"time"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
)
const (
CompositeRouteMatchExact = "exact"
CompositeRouteMatchPrefix = "prefix"
CompositeRouteEndpointAny = "any"
CompositeRouteEndpointMessages = "messages"
CompositeRouteEndpointCountTokens = "count_tokens"
CompositeRouteEndpointResponses = "responses"
CompositeRouteEndpointChatCompletions = "chat_completions"
CompositeRouteEndpointEmbeddings = "embeddings"
CompositeRouteEndpointImages = "images"
CompositeRouteEndpointGemini = "gemini"
CompositeRouteSourceExplicit = "route"
CompositeRouteSourceDetector = "detector"
)
var (
ErrCompositeRouteNotFound = infraerrors.NotFound("COMPOSITE_ROUTE_NOT_FOUND", "composite route not found")
ErrCompositeRouteExists = infraerrors.Conflict("COMPOSITE_ROUTE_EXISTS", "composite route already exists")
)
// CompositeModelRoute maps one public model identifier in a composite group to
// the concrete provider/model that should handle the request.
type CompositeModelRoute struct {
ID int64 `json:"id"`
GroupID int64 `json:"group_id"`
PublicModel string `json:"public_model"`
MatchType string `json:"match_type"`
TargetPlatform string `json:"target_platform"`
UpstreamModel string `json:"upstream_model"`
Endpoint string `json:"endpoint"`
Priority int `json:"priority"`
Enabled bool `json:"enabled"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type CompositeRoutePreviewRequest struct {
Model string `json:"model"`
Endpoint string `json:"endpoint"`
}
type CompositeRouteDecision struct {
Matched bool `json:"matched"`
Source string `json:"source"`
GroupID int64 `json:"group_id"`
PublicModel string `json:"public_model"`
TargetPlatform string `json:"target_platform"`
UpstreamModel string `json:"upstream_model"`
Endpoint string `json:"endpoint"`
Route *CompositeModelRoute `json:"route,omitempty"`
Reason string `json:"reason,omitempty"`
}
type CompositeRouteInput struct {
PublicModel string
MatchType string
TargetPlatform string
UpstreamModel string
Endpoint string
Priority int
Enabled bool
Notes string
}
type CompositeModelRouteRepository interface {
ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error)
Create(ctx context.Context, route *CompositeModelRoute) error
Update(ctx context.Context, route *CompositeModelRoute) error
Delete(ctx context.Context, id int64) error
DeleteByGroup(ctx context.Context, groupID int64) error
}
func normalizeCompositeRouteEndpoint(endpoint string) string {
endpoint = strings.ToLower(strings.TrimSpace(endpoint))
if endpoint == "" {
return CompositeRouteEndpointAny
}
switch endpoint {
case CompositeRouteEndpointMessages,
CompositeRouteEndpointCountTokens,
CompositeRouteEndpointResponses,
CompositeRouteEndpointChatCompletions,
CompositeRouteEndpointEmbeddings,
CompositeRouteEndpointImages,
CompositeRouteEndpointGemini:
return endpoint
default:
return CompositeRouteEndpointAny
}
}
func normalizeCompositeRouteMatchType(matchType string) string {
matchType = strings.ToLower(strings.TrimSpace(matchType))
switch matchType {
case CompositeRouteMatchPrefix:
return CompositeRouteMatchPrefix
default:
return CompositeRouteMatchExact
}
}
func normalizeCompositeRouteInput(input CompositeRouteInput) CompositeRouteInput {
input.PublicModel = strings.TrimSpace(input.PublicModel)
input.MatchType = normalizeCompositeRouteMatchType(input.MatchType)
input.TargetPlatform = strings.TrimSpace(input.TargetPlatform)
input.UpstreamModel = strings.TrimSpace(input.UpstreamModel)
input.Endpoint = normalizeCompositeRouteEndpoint(input.Endpoint)
if input.UpstreamModel == "" {
input.UpstreamModel = input.PublicModel
}
input.Notes = strings.TrimSpace(input.Notes)
return input
}
@@ -31,6 +31,59 @@ func ResolvedTargetPlatformFromContext(ctx context.Context) (string, bool) {
return platform, true
}
func WithCompositeRouteDecision(ctx context.Context, decision CompositeRouteDecision) context.Context {
if ctx == nil || !decision.Matched {
return ctx
}
ctx = WithResolvedTargetPlatform(ctx, decision.TargetPlatform)
if model := strings.TrimSpace(decision.UpstreamModel); model != "" {
ctx = context.WithValue(ctx, ctxkey.ResolvedUpstreamModel, model)
}
if model := strings.TrimSpace(decision.PublicModel); model != "" {
ctx = context.WithValue(ctx, ctxkey.RequestedPublicModel, model)
}
if source := strings.TrimSpace(decision.Source); source != "" {
ctx = context.WithValue(ctx, ctxkey.CompositeRouteSource, source)
}
return ctx
}
func ResolvedUpstreamModelFromContext(ctx context.Context) (string, bool) {
if ctx == nil {
return "", false
}
model, ok := ctx.Value(ctxkey.ResolvedUpstreamModel).(string)
model = strings.TrimSpace(model)
if !ok || model == "" {
return "", false
}
return model, true
}
func RequestedPublicModelFromContext(ctx context.Context) (string, bool) {
if ctx == nil {
return "", false
}
model, ok := ctx.Value(ctxkey.RequestedPublicModel).(string)
model = strings.TrimSpace(model)
if !ok || model == "" {
return "", false
}
return model, true
}
func CompositeRouteSourceFromContext(ctx context.Context) (string, bool) {
if ctx == nil {
return "", false
}
source, ok := ctx.Value(ctxkey.CompositeRouteSource).(string)
source = strings.TrimSpace(source)
if !ok || source == "" {
return "", false
}
return source, true
}
// DetectModelPlatform maps common public model IDs to the concrete provider
// platform used by sub2api. It intentionally returns false for ambiguous model
// names so composite groups fail closed instead of guessing.
@@ -94,6 +147,36 @@ func hasOpenAISeriesPrefix(model string) bool {
return false
}
func (s *GatewayService) resolveCompositeRouteDecision(ctx context.Context, group *Group, requestedModel, endpoint string) (CompositeRouteDecision, bool, error) {
if group == nil || group.Platform != PlatformComposite {
return CompositeRouteDecision{}, false, nil
}
if platform, ok := ResolvedTargetPlatformFromContext(ctx); ok {
upstreamModel := requestedModel
if resolvedModel, modelOK := ResolvedUpstreamModelFromContext(ctx); modelOK {
upstreamModel = resolvedModel
}
source := CompositeRouteSourceDetector
if resolvedSource, sourceOK := CompositeRouteSourceFromContext(ctx); sourceOK {
source = resolvedSource
}
return CompositeRouteDecision{
Matched: true,
Source: source,
GroupID: group.ID,
PublicModel: requestedModel,
TargetPlatform: platform,
UpstreamModel: upstreamModel,
Endpoint: normalizeCompositeRouteEndpoint(endpoint),
}, true, nil
}
decision, err := s.compositeResolver.Resolve(ctx, group.ID, requestedModel, endpoint)
if err != nil {
return decision, false, err
}
return decision, decision.Matched, nil
}
func resolveCompositeTargetPlatform(ctx context.Context, group *Group, requestedModel string) (string, bool) {
if platform, ok := ResolvedTargetPlatformFromContext(ctx); ok {
return platform, true
@@ -49,10 +49,17 @@ func TestQuotaPlatformCompositeUsesResolvedOrForceOnly(t *testing.T) {
require.Equal(t, PlatformAntigravity, QuotaPlatform(ctx, apiKey))
}
func TestSchedulerPlatformsForCompositeGroup(t *testing.T) {
func TestCompositeGroupSchedulerHasAllCanonicalPlatformBuckets(t *testing.T) {
seen := make(map[string]struct{})
for _, bucket := range schedulerCanonicalBuckets(99) {
seen[bucket.Platform] = struct{}{}
}
platforms := make([]string, 0, len(seen))
for platform := range seen {
platforms = append(platforms, platform)
}
require.ElementsMatch(t,
[]string{PlatformAnthropic, PlatformGemini, PlatformOpenAI, PlatformAntigravity, PlatformGrok},
schedulerPlatformsForGroup(PlatformComposite),
platforms,
)
require.Equal(t, []string{PlatformAnthropic}, schedulerPlatformsForGroup(PlatformAnthropic))
}
@@ -0,0 +1,140 @@
package service
import (
"context"
"fmt"
"sort"
"strings"
)
type CompositeRouteResolver struct {
repo CompositeModelRouteRepository
}
func NewCompositeRouteResolver(repo CompositeModelRouteRepository) *CompositeRouteResolver {
return &CompositeRouteResolver{repo: repo}
}
func (r *CompositeRouteResolver) Resolve(ctx context.Context, groupID int64, model, endpoint string) (CompositeRouteDecision, error) {
model = strings.TrimSpace(model)
endpoint = normalizeCompositeRouteEndpoint(endpoint)
decision := CompositeRouteDecision{
GroupID: groupID,
PublicModel: model,
Endpoint: endpoint,
}
if model == "" {
decision.Reason = "model is required"
return decision, nil
}
if r != nil && r.repo != nil && groupID > 0 {
routes, err := r.repo.ListByGroup(ctx, groupID, false)
if err != nil {
return decision, fmt.Errorf("list composite routes: %w", err)
}
if route, ok := matchCompositeRoute(routes, model, endpoint); ok {
upstreamModel := strings.TrimSpace(route.UpstreamModel)
if upstreamModel == "" {
upstreamModel = model
}
return CompositeRouteDecision{
Matched: true,
Source: CompositeRouteSourceExplicit,
GroupID: groupID,
PublicModel: model,
TargetPlatform: route.TargetPlatform,
UpstreamModel: upstreamModel,
Endpoint: endpoint,
Route: &route,
}, nil
}
}
if platform, ok := DetectModelPlatform(model); ok {
return CompositeRouteDecision{
Matched: true,
Source: CompositeRouteSourceDetector,
GroupID: groupID,
PublicModel: model,
TargetPlatform: platform,
UpstreamModel: model,
Endpoint: endpoint,
}, nil
}
decision.Reason = "no explicit route or built-in detector match"
return decision, nil
}
func matchCompositeRoute(routes []CompositeModelRoute, model, endpoint string) (CompositeModelRoute, bool) {
if len(routes) == 0 {
return CompositeModelRoute{}, false
}
type candidate struct {
route CompositeModelRoute
matchStrength int
endpointWeight int
prefixLen int
}
candidates := make([]candidate, 0, len(routes))
for _, route := range routes {
route.Endpoint = normalizeCompositeRouteEndpoint(route.Endpoint)
if route.Endpoint != endpoint && route.Endpoint != CompositeRouteEndpointAny {
continue
}
route.MatchType = normalizeCompositeRouteMatchType(route.MatchType)
publicModel := strings.TrimSpace(route.PublicModel)
if publicModel == "" {
continue
}
matchStrength := 0
prefixLen := len(publicModel)
switch route.MatchType {
case CompositeRouteMatchExact:
if publicModel != model {
continue
}
matchStrength = 2
case CompositeRouteMatchPrefix:
if !strings.HasPrefix(model, publicModel) {
continue
}
matchStrength = 1
default:
continue
}
endpointWeight := 0
if route.Endpoint == endpoint {
endpointWeight = 1
}
candidates = append(candidates, candidate{
route: route,
matchStrength: matchStrength,
endpointWeight: endpointWeight,
prefixLen: prefixLen,
})
}
if len(candidates) == 0 {
return CompositeModelRoute{}, false
}
sort.SliceStable(candidates, func(i, j int) bool {
a, b := candidates[i], candidates[j]
if a.matchStrength != b.matchStrength {
return a.matchStrength > b.matchStrength
}
if a.endpointWeight != b.endpointWeight {
return a.endpointWeight > b.endpointWeight
}
if a.prefixLen != b.prefixLen {
return a.prefixLen > b.prefixLen
}
if a.route.Priority != b.route.Priority {
return a.route.Priority < b.route.Priority
}
return a.route.ID < b.route.ID
})
return candidates[0].route, true
}
@@ -0,0 +1,135 @@
package service
import (
"context"
"testing"
"github.com/stretchr/testify/require"
)
type compositeRouteRepoStub struct {
routes []CompositeModelRoute
}
func (s compositeRouteRepoStub) ListByGroup(ctx context.Context, groupID int64, includeDisabled bool) ([]CompositeModelRoute, error) {
routes := make([]CompositeModelRoute, 0, len(s.routes))
for _, route := range s.routes {
if route.GroupID != groupID {
continue
}
if !includeDisabled && !route.Enabled {
continue
}
routes = append(routes, route)
}
return routes, nil
}
func (s compositeRouteRepoStub) Create(ctx context.Context, route *CompositeModelRoute) error {
return nil
}
func (s compositeRouteRepoStub) Update(ctx context.Context, route *CompositeModelRoute) error {
return nil
}
func (s compositeRouteRepoStub) Delete(ctx context.Context, id int64) error {
return nil
}
func (s compositeRouteRepoStub) DeleteByGroup(ctx context.Context, groupID int64) error {
return nil
}
func TestCompositeRouteResolverExplicitExactRouteRewritesModel(t *testing.T) {
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
routes: []CompositeModelRoute{
{
ID: 10,
GroupID: 7,
PublicModel: "openrouter/gpt-5",
MatchType: CompositeRouteMatchExact,
TargetPlatform: PlatformOpenAI,
UpstreamModel: "gpt-5",
Endpoint: CompositeRouteEndpointAny,
Priority: 100,
Enabled: true,
},
},
})
decision, err := resolver.Resolve(context.Background(), 7, "openrouter/gpt-5", CompositeRouteEndpointChatCompletions)
require.NoError(t, err)
require.True(t, decision.Matched)
require.Equal(t, CompositeRouteSourceExplicit, decision.Source)
require.Equal(t, PlatformOpenAI, decision.TargetPlatform)
require.Equal(t, "gpt-5", decision.UpstreamModel)
require.NotNil(t, decision.Route)
require.Equal(t, int64(10), decision.Route.ID)
}
func TestCompositeRouteResolverPrefersEndpointSpecificLongestPrefix(t *testing.T) {
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
routes: []CompositeModelRoute{
{
ID: 1,
GroupID: 7,
PublicModel: "router/",
MatchType: CompositeRouteMatchPrefix,
TargetPlatform: PlatformAnthropic,
Endpoint: CompositeRouteEndpointAny,
Priority: 10,
Enabled: true,
},
{
ID: 2,
GroupID: 7,
PublicModel: "router/gpt-",
MatchType: CompositeRouteMatchPrefix,
TargetPlatform: PlatformOpenAI,
UpstreamModel: "gpt-family",
Endpoint: CompositeRouteEndpointResponses,
Priority: 100,
Enabled: true,
},
},
})
decision, err := resolver.Resolve(context.Background(), 7, "router/gpt-5", CompositeRouteEndpointResponses)
require.NoError(t, err)
require.True(t, decision.Matched)
require.Equal(t, CompositeRouteSourceExplicit, decision.Source)
require.Equal(t, PlatformOpenAI, decision.TargetPlatform)
require.Equal(t, "gpt-family", decision.UpstreamModel)
require.NotNil(t, decision.Route)
require.Equal(t, int64(2), decision.Route.ID)
}
func TestCompositeRouteResolverIgnoresDisabledRoutesAndFallsBackToDetector(t *testing.T) {
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
routes: []CompositeModelRoute{
{
ID: 1,
GroupID: 7,
PublicModel: "gpt-5",
MatchType: CompositeRouteMatchExact,
TargetPlatform: PlatformAnthropic,
UpstreamModel: "claude-sonnet-4-6",
Endpoint: CompositeRouteEndpointAny,
Priority: 100,
Enabled: false,
},
},
})
decision, err := resolver.Resolve(context.Background(), 7, "gpt-5", CompositeRouteEndpointAny)
require.NoError(t, err)
require.True(t, decision.Matched)
require.Equal(t, CompositeRouteSourceDetector, decision.Source)
require.Equal(t, PlatformOpenAI, decision.TargetPlatform)
require.Equal(t, "gpt-5", decision.UpstreamModel)
require.Nil(t, decision.Route)
}
@@ -44,6 +44,7 @@ func newGatewayRecordUsageServiceForTest(usageRepo UsageLogRepository, userRepo
nil,
nil,
nil,
nil,
nil, // userPlatformQuotaRepo
)
}
+17 -7
View File
@@ -46,12 +46,16 @@ func (s *GatewayService) SelectAccountForModelWithExclusions(ctx context.Context
ctx = s.withGroupContext(ctx, group)
platform = group.Platform
if group != nil && group.Platform == PlatformComposite {
targetPlatform, ok := resolveCompositeTargetPlatform(ctx, group, requestedModel)
decision, ok, err := s.resolveCompositeRouteDecision(ctx, group, requestedModel, CompositeRouteEndpointAny)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("%w supporting model: %s (composite target platform unknown)", ErrNoAvailableAccounts, requestedModel)
}
platform = targetPlatform
ctx = WithResolvedTargetPlatform(ctx, targetPlatform)
platform = decision.TargetPlatform
requestedModel = decision.UpstreamModel
ctx = WithCompositeRouteDecision(ctx, decision)
}
} else {
// 无分组时只使用原生 anthropic 平台
@@ -908,11 +912,14 @@ func (s *GatewayService) resolvePlatform(ctx context.Context, groupID *int64, gr
}
if group != nil {
if group.Platform == PlatformComposite {
targetPlatform, ok := resolveCompositeTargetPlatform(ctx, group, requestedModel)
decision, ok, err := s.resolveCompositeRouteDecision(ctx, group, requestedModel, CompositeRouteEndpointAny)
if err != nil {
return "", false, err
}
if !ok {
return "", false, fmt.Errorf("%w supporting model: %s (composite target platform unknown)", ErrNoAvailableAccounts, requestedModel)
}
return targetPlatform, false, nil
return decision.TargetPlatform, false, nil
}
return group.Platform, false, nil
}
@@ -922,11 +929,14 @@ func (s *GatewayService) resolvePlatform(ctx context.Context, groupID *int64, gr
return "", false, err
}
if group.Platform == PlatformComposite {
targetPlatform, ok := resolveCompositeTargetPlatform(ctx, group, requestedModel)
decision, ok, err := s.resolveCompositeRouteDecision(ctx, group, requestedModel, CompositeRouteEndpointAny)
if err != nil {
return "", false, err
}
if !ok {
return "", false, fmt.Errorf("%w supporting model: %s (composite target platform unknown)", ErrNoAvailableAccounts, requestedModel)
}
return targetPlatform, false, nil
return decision.TargetPlatform, false, nil
}
return group.Platform, false, nil
}
@@ -708,6 +708,7 @@ type GatewayService struct {
debugClaudeMimic atomic.Bool
channelService *ChannelService
resolver *ModelPricingResolver
compositeResolver *CompositeRouteResolver
debugGatewayBodyFile atomic.Pointer[os.File] // non-nil when SUB2API_DEBUG_GATEWAY_BODY is set
tlsFPProfileService *TLSFingerprintProfileService
balanceNotifyService *BalanceNotifyService
@@ -741,6 +742,7 @@ func NewGatewayService(
tlsFPProfileService *TLSFingerprintProfileService,
channelService *ChannelService,
resolver *ModelPricingResolver,
compositeResolver *CompositeRouteResolver,
balanceNotifyService *BalanceNotifyService,
userPlatformQuotaRepo UserPlatformQuotaRepository,
) *GatewayService {
@@ -777,6 +779,7 @@ func NewGatewayService(
tlsFPProfileService: tlsFPProfileService,
channelService: channelService,
resolver: resolver,
compositeResolver: compositeResolver,
balanceNotifyService: balanceNotifyService,
userPlatformQuotaRepo: userPlatformQuotaRepo,
}
+1
View File
@@ -681,6 +681,7 @@ var ProviderSet = wire.NewSet(
ProvideAPIKeyAuthCacheInvalidator,
ProvideAuthCacheInvalidationWorker,
NewGroupService,
NewCompositeRouteResolver,
NewAccountService,
NewProxyService,
NewRedeemService,
@@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS composite_model_routes (
id BIGSERIAL PRIMARY KEY,
group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
public_model VARCHAR(200) NOT NULL,
match_type VARCHAR(20) NOT NULL DEFAULT 'exact',
target_platform VARCHAR(50) NOT NULL,
upstream_model VARCHAR(200) NOT NULL DEFAULT '',
endpoint VARCHAR(50) NOT NULL DEFAULT 'any',
priority INTEGER NOT NULL DEFAULT 100,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL,
CONSTRAINT composite_model_routes_match_type_check CHECK (match_type IN ('exact', 'prefix')),
CONSTRAINT composite_model_routes_endpoint_check CHECK (endpoint IN ('any', 'messages', 'count_tokens', 'responses', 'chat_completions', 'embeddings', 'images', 'gemini')),
CONSTRAINT composite_model_routes_target_platform_check CHECK (target_platform IN ('anthropic', 'openai', 'gemini', 'antigravity', 'grok'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_composite_model_routes_unique_active
ON composite_model_routes (group_id, endpoint, match_type, public_model)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_composite_model_routes_group_enabled
ON composite_model_routes (group_id, enabled)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_composite_model_routes_group_priority
ON composite_model_routes (group_id, priority, id)
WHERE deleted_at IS NULL;
+42 -6
View File
@@ -2,7 +2,8 @@
Composite groups are an admin routing layer for API keys that should choose a
concrete provider from the requested model instead of binding the key to a
single provider group.
single provider group. They support both built-in model detection and an
admin-configured model route registry for public model aliases.
## Supported Providers
@@ -18,7 +19,41 @@ The selected concrete platform is used for account selection, user platform
quota checks, post-usage billing, ops error platform attribution, channel
mapping/pricing lookup, and platform usage reporting.
## Model Detection
## Route Registry
Admins can configure routes on a composite group from the group list's
`Routes` action or through the admin API:
- `GET /api/v1/admin/groups/:id/composite-routes`
- `POST /api/v1/admin/groups/:id/composite-routes`
- `PUT /api/v1/admin/groups/:id/composite-routes/:route_id`
- `DELETE /api/v1/admin/groups/:id/composite-routes/:route_id`
- `POST /api/v1/admin/groups/:id/composite-routes/preview`
Each route belongs to one composite group and contains:
- `public_model`: model identifier the client sends.
- `match_type`: `exact` or `prefix`.
- `target_platform`: concrete provider platform.
- `upstream_model`: model identifier sent upstream. If omitted, the public
model is reused.
- `endpoint`: `any`, `messages`, `count_tokens`, `responses`,
`chat_completions`, `embeddings`, `images`, or `gemini`.
- `priority`: lower values win after match specificity.
- `enabled`: disabled routes are ignored by runtime resolution but remain
visible to admins.
Resolution order is explicit route first, then built-in detection. When more
than one explicit route matches, exact matches beat prefix matches,
endpoint-specific routes beat `any`, longer prefixes beat shorter prefixes,
then lower `priority`, then lower route id.
For JSON-body endpoints, the gateway rewrites the request `model` field to the
route's `upstream_model` before dispatch. For Gemini native paths such as
`/v1beta/models/{model}:generateContent`, the gateway resolves `{model}` and
the handler forwards the resolved upstream model.
## Built-In Detection
Composite routing detects common public model IDs and provider-prefixed IDs:
@@ -34,6 +69,7 @@ guessing a provider.
## Admin Workflows
- Admins can create a group with platform `composite`.
- Admins can add, edit, delete, and preview composite model routes.
- Composite groups can copy accounts from concrete provider groups.
- Concrete provider accounts can be assigned directly to composite groups from
account create/edit and bulk account workflows.
@@ -43,7 +79,7 @@ guessing a provider.
## Limits
Composite groups are not a full OpenRouter-compatible model registry. They do
not add a provider/model mapping database, per-model admin routing overrides, or
arbitrary third-party provider prefixes. Add those explicitly before relying on
custom model IDs that cannot be detected from their names.
Composite routes choose a concrete provider and upstream model; they do not
create synthetic model metadata, pricing, or upstream capability records by
themselves. Keep channel pricing/model mapping configured for the concrete
provider platforms that the routes target.
+58
View File
@@ -7,6 +7,10 @@ import { apiClient } from '../client'
import type {
AdminGroup,
GroupPlatform,
CompositeModelRoute,
CompositeModelRouteInput,
CompositeRoutePreviewRequest,
CompositeRouteDecision,
CreateGroupRequest,
UpdateGroupRequest,
PaginatedResponse
@@ -263,6 +267,55 @@ export async function getGroupApiKeys(
return data
}
export async function listCompositeRoutes(id: number): Promise<CompositeModelRoute[]> {
const { data } = await apiClient.get<CompositeModelRoute[]>(`/admin/groups/${id}/composite-routes`)
return data
}
export async function createCompositeRoute(
id: number,
route: CompositeModelRouteInput
): Promise<CompositeModelRoute> {
const { data } = await apiClient.post<CompositeModelRoute>(
`/admin/groups/${id}/composite-routes`,
route
)
return data
}
export async function updateCompositeRoute(
id: number,
routeId: number,
route: CompositeModelRouteInput
): Promise<CompositeModelRoute> {
const { data } = await apiClient.put<CompositeModelRoute>(
`/admin/groups/${id}/composite-routes/${routeId}`,
route
)
return data
}
export async function deleteCompositeRoute(
id: number,
routeId: number
): Promise<{ message: string }> {
const { data } = await apiClient.delete<{ message: string }>(
`/admin/groups/${id}/composite-routes/${routeId}`
)
return data
}
export async function previewCompositeRoute(
id: number,
request: CompositeRoutePreviewRequest
): Promise<CompositeRouteDecision> {
const { data } = await apiClient.post<CompositeRouteDecision>(
`/admin/groups/${id}/composite-routes/preview`,
request
)
return data
}
/**
* Rate multiplier entry for a user in a group
*/
@@ -423,6 +476,11 @@ export const groupsAPI = {
toggleStatus,
getStats,
getGroupApiKeys,
listCompositeRoutes,
createCompositeRoute,
updateCompositeRoute,
deleteCompositeRoute,
previewCompositeRoute,
getGroupRateMultipliers,
clearGroupRateMultipliers,
batchSetGroupRateMultipliers,
@@ -1015,6 +1015,55 @@ export default {
selectAll: 'Select all',
invertSelection: 'Invert'
},
compositeRoutes: {
action: 'Routes',
title: 'Composite Routes',
titleWithGroup: 'Composite Routes: {name}',
routes: 'Saved Routes',
empty: 'No composite routes configured',
publicModel: 'Public Model',
target: 'Target',
scope: 'Scope',
priority: 'Priority',
addRoute: 'Add Route',
editRoute: 'Edit Route',
matchType: 'Match',
endpoint: 'Endpoint',
targetPlatform: 'Target Platform',
upstreamModel: 'Upstream Model',
notes: 'Notes',
enabled: 'Enabled',
preview: 'Preview',
matched: 'Matched',
notMatched: 'No Match',
publicModelRequired: 'Public model is required',
routeCreated: 'Composite route created',
routeUpdated: 'Composite route updated',
routeDeleted: 'Composite route deleted',
failedToLoad: 'Failed to load composite routes',
failedToSave: 'Failed to save composite route',
failedToDelete: 'Failed to delete composite route',
failedToPreview: 'Failed to preview composite route',
deleteConfirm: 'Delete this composite route?',
endpoints: {
any: 'Any',
messages: 'Messages',
countTokens: 'Count Tokens',
responses: 'Responses',
chatCompletions: 'Chat Completions',
embeddings: 'Embeddings',
images: 'Images',
gemini: 'Gemini Native'
},
match: {
exact: 'Exact',
prefix: 'Prefix'
},
sources: {
route: 'Route',
detector: 'Detector'
}
},
claudeCode: {
title: 'Claude Code Client Restriction',
tooltip: 'When enabled, this group only allows official Claude Code clients. Non-Claude Code requests will be rejected or fallback to the specified group.',
@@ -1012,6 +1012,55 @@ export default {
selectAll: '全选',
invertSelection: '反选'
},
compositeRoutes: {
action: '路由',
title: 'Composite 路由',
titleWithGroup: 'Composite 路由:{name}',
routes: '已保存路由',
empty: '暂无 Composite 路由',
publicModel: '公开模型',
target: '目标',
scope: '范围',
priority: '优先级',
addRoute: '添加路由',
editRoute: '编辑路由',
matchType: '匹配方式',
endpoint: '端点',
targetPlatform: '目标平台',
upstreamModel: '上游模型',
notes: '备注',
enabled: '启用',
preview: '预览',
matched: '已匹配',
notMatched: '未匹配',
publicModelRequired: '请输入公开模型',
routeCreated: 'Composite 路由已创建',
routeUpdated: 'Composite 路由已更新',
routeDeleted: 'Composite 路由已删除',
failedToLoad: '加载 Composite 路由失败',
failedToSave: '保存 Composite 路由失败',
failedToDelete: '删除 Composite 路由失败',
failedToPreview: '预览 Composite 路由失败',
deleteConfirm: '确定删除此 Composite 路由?',
endpoints: {
any: '任意',
messages: 'Messages',
countTokens: 'Count Tokens',
responses: 'Responses',
chatCompletions: 'Chat Completions',
embeddings: 'Embeddings',
images: 'Images',
gemini: 'Gemini 原生'
},
match: {
exact: '精确',
prefix: '前缀'
},
sources: {
route: '路由',
detector: '内置识别'
}
},
claudeCode: {
title: 'Claude Code 客户端限制',
tooltip:
+57
View File
@@ -589,6 +589,63 @@ export interface ModelsListConfig {
models: string[]
}
export type CompositeRouteMatchType = 'exact' | 'prefix'
export type CompositeRouteEndpoint =
| 'any'
| 'messages'
| 'count_tokens'
| 'responses'
| 'chat_completions'
| 'embeddings'
| 'images'
| 'gemini'
export type CompositeRouteSource = 'route' | 'detector' | string
export interface CompositeModelRoute {
id: number
group_id: number
public_model: string
match_type: CompositeRouteMatchType
target_platform: Exclude<GroupPlatform, 'composite'>
upstream_model: string
endpoint: CompositeRouteEndpoint
priority: number
enabled: boolean
notes: string
created_at?: string
updated_at?: string
}
export interface CompositeModelRouteInput {
public_model: string
match_type: CompositeRouteMatchType
target_platform: Exclude<GroupPlatform, 'composite'>
upstream_model?: string
endpoint: CompositeRouteEndpoint
priority?: number
enabled?: boolean
notes?: string
}
export interface CompositeRoutePreviewRequest {
model: string
endpoint: CompositeRouteEndpoint
}
export interface CompositeRouteDecision {
matched: boolean
source: CompositeRouteSource
group_id: number
public_model: string
target_platform: Exclude<GroupPlatform, 'composite'> | ''
upstream_model: string
endpoint: CompositeRouteEndpoint
route?: CompositeModelRoute
reason?: string
}
export interface ApiKey {
id: number
user_id: number
+629 -1
View File
@@ -386,6 +386,16 @@
}}
</span>
</button>
<button
v-if="row.platform === 'composite'"
@click="handleCompositeRoutes(row)"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-cyan-600 dark:hover:bg-dark-700 dark:hover:text-cyan-400"
>
<Icon name="swap" size="sm" />
<span class="text-xs">{{
t("admin.groups.compositeRoutes.action")
}}</span>
</button>
<button
@click="handleRateMultipliers(row)"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-purple-600 dark:hover:bg-dark-700 dark:hover:text-purple-400"
@@ -3578,6 +3588,362 @@
</template>
</BaseDialog>
<!-- Composite Routes Modal -->
<BaseDialog
:show="showCompositeRoutesModal"
:title="
compositeRoutesGroup
? t('admin.groups.compositeRoutes.titleWithGroup', {
name: compositeRoutesGroup.name,
})
: t('admin.groups.compositeRoutes.title')
"
width="wide"
@close="closeCompositeRoutesModal"
>
<div class="grid gap-5 lg:grid-cols-[minmax(0,1.15fr)_minmax(320px,0.85fr)]">
<section class="min-w-0">
<div class="mb-3 flex items-center justify-between gap-3">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
{{ t("admin.groups.compositeRoutes.routes") }}
</h3>
<button
type="button"
class="btn btn-secondary btn-sm"
:disabled="compositeRoutesLoading"
@click="loadCompositeRoutes"
>
<Icon
name="refresh"
size="sm"
:class="compositeRoutesLoading ? 'animate-spin' : ''"
/>
</button>
</div>
<div
class="overflow-hidden rounded-lg border border-gray-200 dark:border-dark-600"
>
<div
v-if="compositeRoutesLoading"
class="flex h-36 items-center justify-center text-sm text-gray-500 dark:text-gray-400"
>
{{ t("common.loading") }}
</div>
<div
v-else-if="compositeRoutes.length === 0"
class="flex h-36 items-center justify-center text-sm text-gray-500 dark:text-gray-400"
>
{{ t("admin.groups.compositeRoutes.empty") }}
</div>
<div v-else class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 text-sm dark:divide-dark-600">
<thead class="bg-gray-50 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:bg-dark-800 dark:text-gray-400">
<tr>
<th class="px-3 py-2">
{{ t("admin.groups.compositeRoutes.publicModel") }}
</th>
<th class="px-3 py-2">
{{ t("admin.groups.compositeRoutes.target") }}
</th>
<th class="px-3 py-2">
{{ t("admin.groups.compositeRoutes.scope") }}
</th>
<th class="px-3 py-2 text-right">
{{ t("admin.groups.columns.actions") }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 bg-white dark:divide-dark-700 dark:bg-dark-900">
<tr
v-for="route in compositeRoutes"
:key="route.id"
:class="!route.enabled && 'opacity-60'"
>
<td class="max-w-[15rem] px-3 py-2">
<div class="break-all font-medium text-gray-900 dark:text-white">
{{ route.public_model }}
</div>
<div class="mt-1 flex flex-wrap items-center gap-1.5">
<span class="badge badge-gray">{{
compositeRouteMatchLabel(route.match_type)
}}</span>
<span
v-if="!route.enabled"
class="badge badge-danger"
>
{{ t("admin.accounts.status.inactive") }}
</span>
</div>
</td>
<td class="px-3 py-2">
<div class="flex items-center gap-1.5 text-gray-900 dark:text-white">
<PlatformIcon :platform="route.target_platform" size="xs" />
<span>{{ formatCompositePlatform(route.target_platform) }}</span>
</div>
<div class="mt-1 break-all text-xs text-gray-500 dark:text-gray-400">
{{ route.upstream_model || route.public_model }}
</div>
</td>
<td class="px-3 py-2">
<div class="text-gray-700 dark:text-gray-300">
{{ formatCompositeEndpoint(route.endpoint) }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
{{ t("admin.groups.compositeRoutes.priority") }}:
{{ route.priority }}
</div>
</td>
<td class="px-3 py-2">
<div class="flex justify-end gap-1">
<button
type="button"
class="rounded p-1.5 text-gray-500 hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
:title="t('common.edit')"
@click="editCompositeRoute(route)"
>
<Icon name="edit" size="sm" />
</button>
<button
type="button"
class="rounded p-1.5 text-gray-500 hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
:title="t('common.delete')"
@click="deleteCompositeRoute(route)"
>
<Icon name="trash" size="sm" />
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="space-y-5">
<form class="space-y-3" @submit.prevent="saveCompositeRoute">
<div class="flex items-center justify-between gap-3">
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">
{{
compositeRouteEditingId
? t("admin.groups.compositeRoutes.editRoute")
: t("admin.groups.compositeRoutes.addRoute")
}}
</h3>
<button
v-if="compositeRouteEditingId"
type="button"
class="text-xs font-medium text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
@click="resetCompositeRouteForm"
>
{{ t("common.cancel") }}
</button>
</div>
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.publicModel")
}}</label>
<input
v-model.trim="compositeRouteForm.public_model"
type="text"
class="input"
required
placeholder="openrouter/gpt-5"
/>
</div>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.matchType")
}}</label>
<Select
v-model="compositeRouteForm.match_type"
:options="compositeRouteMatchOptions"
/>
</div>
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.endpoint")
}}</label>
<Select
v-model="compositeRouteForm.endpoint"
:options="compositeRouteEndpointOptions"
/>
</div>
</div>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.targetPlatform")
}}</label>
<Select
v-model="compositeRouteForm.target_platform"
:options="compositeRoutePlatformOptions"
/>
</div>
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.priority")
}}</label>
<input
v-model.number="compositeRouteForm.priority"
type="number"
min="1"
step="1"
class="input"
/>
</div>
</div>
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.upstreamModel")
}}</label>
<input
v-model.trim="compositeRouteForm.upstream_model"
type="text"
class="input"
placeholder="gpt-5"
/>
</div>
<div>
<label class="input-label">{{
t("admin.groups.compositeRoutes.notes")
}}</label>
<textarea
v-model.trim="compositeRouteForm.notes"
rows="2"
class="input"
></textarea>
</div>
<div class="flex items-center justify-between gap-3">
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
<input
v-model="compositeRouteForm.enabled"
type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 dark:border-dark-600 dark:bg-dark-700"
/>
{{ t("admin.groups.compositeRoutes.enabled") }}
</label>
<button
type="submit"
class="btn btn-primary"
:disabled="compositeRouteSaving"
>
<Icon
v-if="!compositeRouteSaving"
name="check"
size="sm"
class="mr-2"
/>
{{ compositeRouteEditingId ? t("common.update") : t("common.create") }}
</button>
</div>
</form>
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">
{{ t("admin.groups.compositeRoutes.preview") }}
</h3>
<div class="space-y-3">
<input
v-model.trim="compositePreviewModel"
type="text"
class="input"
placeholder="openrouter/gpt-5"
@keyup.enter="previewCompositeRoute"
/>
<div class="flex gap-2">
<Select
v-model="compositePreviewEndpoint"
:options="compositeRouteEndpointOptions"
class="min-w-0 flex-1"
/>
<button
type="button"
class="btn btn-secondary"
:disabled="compositePreviewLoading || !compositePreviewModel"
@click="previewCompositeRoute"
>
<Icon name="play" size="sm" />
</button>
</div>
<div
v-if="compositePreviewDecision"
class="rounded-lg border border-gray-200 bg-gray-50 p-3 text-sm dark:border-dark-600 dark:bg-dark-800"
>
<div class="mb-2 flex items-center gap-2">
<span
:class="[
'badge',
compositePreviewDecision.matched
? 'badge-success'
: 'badge-danger',
]"
>
{{
compositePreviewDecision.matched
? t("admin.groups.compositeRoutes.matched")
: t("admin.groups.compositeRoutes.notMatched")
}}
</span>
<span class="badge badge-gray">
{{
compositeRouteSourceLabel(
compositePreviewDecision.source,
)
}}
</span>
</div>
<div
v-if="compositePreviewDecision.matched"
class="space-y-1 text-gray-700 dark:text-gray-300"
>
<div>
{{ t("admin.groups.compositeRoutes.targetPlatform") }}:
{{
formatCompositePlatform(
compositePreviewDecision.target_platform,
)
}}
</div>
<div class="break-all">
{{ t("admin.groups.compositeRoutes.upstreamModel") }}:
{{ compositePreviewDecision.upstream_model }}
</div>
</div>
<div
v-else
class="text-gray-500 dark:text-gray-400"
>
{{ compositePreviewDecision.reason }}
</div>
</div>
</div>
</div>
</section>
</div>
<template #footer>
<div class="flex justify-end pt-4">
<button
type="button"
class="btn btn-secondary"
@click="closeCompositeRoutesModal"
>
{{ t("common.close") }}
</button>
</div>
</template>
</BaseDialog>
<!-- Group Rate Multipliers Modal -->
<GroupRateMultipliersModal
:show="showRateMultipliersModal"
@@ -3602,7 +3968,16 @@ import { useI18n } from "vue-i18n";
import { useAppStore } from "@/stores/app";
import { useOnboardingStore } from "@/stores/onboarding";
import { adminAPI } from "@/api/admin";
import type { AdminGroup, GroupPlatform, SubscriptionType } from "@/types";
import type {
AdminGroup,
CompositeModelRoute,
CompositeModelRouteInput,
CompositeRouteDecision,
CompositeRouteEndpoint,
CompositeRouteMatchType,
GroupPlatform,
SubscriptionType,
} from "@/types";
import type { Column } from "@/components/common/types";
import AppLayout from "@/components/layout/AppLayout.vue";
import TablePageLayout from "@/components/layout/TablePageLayout.vue";
@@ -3854,6 +4229,45 @@ const platformFilterOptions = computed(() => [
{ value: "composite", label: "Composite" },
]);
const compositeRoutePlatformOptions = computed(() => [
{ value: "anthropic", label: "Anthropic" },
{ value: "openai", label: "OpenAI" },
{ value: "gemini", label: "Gemini" },
{ value: "antigravity", label: "Antigravity" },
{ value: "grok", label: "Grok" },
]);
const compositeRouteEndpointOptions = computed(() => [
{ value: "any", label: t("admin.groups.compositeRoutes.endpoints.any") },
{
value: "messages",
label: t("admin.groups.compositeRoutes.endpoints.messages"),
},
{
value: "count_tokens",
label: t("admin.groups.compositeRoutes.endpoints.countTokens"),
},
{
value: "responses",
label: t("admin.groups.compositeRoutes.endpoints.responses"),
},
{
value: "chat_completions",
label: t("admin.groups.compositeRoutes.endpoints.chatCompletions"),
},
{
value: "embeddings",
label: t("admin.groups.compositeRoutes.endpoints.embeddings"),
},
{ value: "images", label: t("admin.groups.compositeRoutes.endpoints.images") },
{ value: "gemini", label: t("admin.groups.compositeRoutes.endpoints.gemini") },
]);
const compositeRouteMatchOptions = computed(() => [
{ value: "exact", label: t("admin.groups.compositeRoutes.match.exact") },
{ value: "prefix", label: t("admin.groups.compositeRoutes.match.prefix") },
]);
const editStatusOptions = computed(() => [
{ value: "active", label: t("admin.accounts.status.active") },
{ value: "inactive", label: t("admin.accounts.status.inactive") },
@@ -4030,6 +4444,38 @@ const rateMultipliersGroup = ref<AdminGroup | null>(null);
const showRPMOverridesModal = ref(false);
const rpmOverridesGroup = ref<AdminGroup | null>(null);
const sortableGroups = ref<AdminGroup[]>([]);
type ConcreteGroupPlatform = Exclude<GroupPlatform, "composite">;
type CompositeRouteFormState = {
public_model: string;
match_type: CompositeRouteMatchType;
target_platform: ConcreteGroupPlatform;
upstream_model: string;
endpoint: CompositeRouteEndpoint;
priority: number;
enabled: boolean;
notes: string;
};
const showCompositeRoutesModal = ref(false);
const compositeRoutesGroup = ref<AdminGroup | null>(null);
const compositeRoutes = ref<CompositeModelRoute[]>([]);
const compositeRoutesLoading = ref(false);
const compositeRouteSaving = ref(false);
const compositeRouteEditingId = ref<number | null>(null);
const compositePreviewModel = ref("");
const compositePreviewEndpoint = ref<CompositeRouteEndpoint>("any");
const compositePreviewLoading = ref(false);
const compositePreviewDecision = ref<CompositeRouteDecision | null>(null);
const compositeRouteForm = reactive<CompositeRouteFormState>({
public_model: "",
match_type: "exact",
target_platform: "openai",
upstream_model: "",
endpoint: "any",
priority: 100,
enabled: true,
notes: "",
});
const createMessagesDispatchDefaults = createDefaultMessagesDispatchFormState();
const editMessagesDispatchDefaults = createDefaultMessagesDispatchFormState();
const createModelsListState = reactive(createInitialModelsListState());
@@ -5247,6 +5693,188 @@ const handleDuplicate = async (group: AdminGroup) => {
}
};
const compositeRouteMatchLabel = (matchType: CompositeRouteMatchType) =>
compositeRouteMatchOptions.value.find((option) => option.value === matchType)
?.label || matchType;
const formatCompositeEndpoint = (endpoint: CompositeRouteEndpoint) =>
compositeRouteEndpointOptions.value.find((option) => option.value === endpoint)
?.label || endpoint;
const formatCompositePlatform = (platform: string) => {
if (!platform) return "—";
return t(`admin.groups.platforms.${platform}`);
};
const compositeRouteSourceLabel = (source: string) => {
if (source === "route") return t("admin.groups.compositeRoutes.sources.route");
if (source === "detector") {
return t("admin.groups.compositeRoutes.sources.detector");
}
return source || "—";
};
const resetCompositeRouteForm = () => {
compositeRouteEditingId.value = null;
compositeRouteForm.public_model = "";
compositeRouteForm.match_type = "exact";
compositeRouteForm.target_platform = "openai";
compositeRouteForm.upstream_model = "";
compositeRouteForm.endpoint = "any";
compositeRouteForm.priority = 100;
compositeRouteForm.enabled = true;
compositeRouteForm.notes = "";
};
const toCompositeRouteInput = (): CompositeModelRouteInput => ({
public_model: compositeRouteForm.public_model.trim(),
match_type: compositeRouteForm.match_type,
target_platform: compositeRouteForm.target_platform,
upstream_model: compositeRouteForm.upstream_model.trim(),
endpoint: compositeRouteForm.endpoint,
priority: Number(compositeRouteForm.priority) || 100,
enabled: compositeRouteForm.enabled,
notes: compositeRouteForm.notes.trim(),
});
const loadCompositeRoutes = async () => {
if (!compositeRoutesGroup.value) return;
compositeRoutesLoading.value = true;
try {
const routes = await adminAPI.groups.listCompositeRoutes(
compositeRoutesGroup.value.id,
);
compositeRoutes.value = routes.sort((a, b) => {
if (a.priority !== b.priority) return a.priority - b.priority;
return a.id - b.id;
});
} catch (error: any) {
appStore.showError(
error.response?.data?.detail ||
error.response?.data?.message ||
t("admin.groups.compositeRoutes.failedToLoad"),
);
console.error("Error loading composite routes:", error);
} finally {
compositeRoutesLoading.value = false;
}
};
const handleCompositeRoutes = async (group: AdminGroup) => {
compositeRoutesGroup.value = group;
compositePreviewModel.value = "";
compositePreviewEndpoint.value = "any";
compositePreviewDecision.value = null;
resetCompositeRouteForm();
showCompositeRoutesModal.value = true;
await loadCompositeRoutes();
};
const closeCompositeRoutesModal = () => {
showCompositeRoutesModal.value = false;
compositeRoutesGroup.value = null;
compositeRoutes.value = [];
compositePreviewDecision.value = null;
resetCompositeRouteForm();
};
const editCompositeRoute = (route: CompositeModelRoute) => {
compositeRouteEditingId.value = route.id;
compositeRouteForm.public_model = route.public_model;
compositeRouteForm.match_type = route.match_type;
compositeRouteForm.target_platform = route.target_platform;
compositeRouteForm.upstream_model = route.upstream_model;
compositeRouteForm.endpoint = route.endpoint;
compositeRouteForm.priority = route.priority || 100;
compositeRouteForm.enabled = route.enabled;
compositeRouteForm.notes = route.notes || "";
};
const saveCompositeRoute = async () => {
if (!compositeRoutesGroup.value) return;
if (!compositeRouteForm.public_model.trim()) {
appStore.showError(t("admin.groups.compositeRoutes.publicModelRequired"));
return;
}
compositeRouteSaving.value = true;
try {
const payload = toCompositeRouteInput();
if (compositeRouteEditingId.value) {
await adminAPI.groups.updateCompositeRoute(
compositeRoutesGroup.value.id,
compositeRouteEditingId.value,
payload,
);
appStore.showSuccess(t("admin.groups.compositeRoutes.routeUpdated"));
} else {
await adminAPI.groups.createCompositeRoute(
compositeRoutesGroup.value.id,
payload,
);
appStore.showSuccess(t("admin.groups.compositeRoutes.routeCreated"));
}
resetCompositeRouteForm();
await loadCompositeRoutes();
} catch (error: any) {
appStore.showError(
error.response?.data?.detail ||
error.response?.data?.message ||
t("admin.groups.compositeRoutes.failedToSave"),
);
console.error("Error saving composite route:", error);
} finally {
compositeRouteSaving.value = false;
}
};
const deleteCompositeRoute = async (route: CompositeModelRoute) => {
if (!compositeRoutesGroup.value) return;
if (!window.confirm(t("admin.groups.compositeRoutes.deleteConfirm"))) return;
try {
await adminAPI.groups.deleteCompositeRoute(
compositeRoutesGroup.value.id,
route.id,
);
if (compositeRouteEditingId.value === route.id) {
resetCompositeRouteForm();
}
appStore.showSuccess(t("admin.groups.compositeRoutes.routeDeleted"));
await loadCompositeRoutes();
} catch (error: any) {
appStore.showError(
error.response?.data?.detail ||
error.response?.data?.message ||
t("admin.groups.compositeRoutes.failedToDelete"),
);
console.error("Error deleting composite route:", error);
}
};
const previewCompositeRoute = async () => {
if (!compositeRoutesGroup.value || !compositePreviewModel.value.trim()) {
return;
}
compositePreviewLoading.value = true;
try {
compositePreviewDecision.value = await adminAPI.groups.previewCompositeRoute(
compositeRoutesGroup.value.id,
{
model: compositePreviewModel.value.trim(),
endpoint: compositePreviewEndpoint.value,
},
);
} catch (error: any) {
appStore.showError(
error.response?.data?.detail ||
error.response?.data?.message ||
t("admin.groups.compositeRoutes.failedToPreview"),
);
console.error("Error previewing composite route:", error);
} finally {
compositePreviewLoading.value = false;
}
};
const handleDelete = (group: AdminGroup) => {
deletingGroup.value = group;
showDeleteDialog.value = true;