diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 7568fa50..9028210c 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -175,7 +175,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { digestSessionStore := service.NewDigestSessionStore() channelRepository := repository.NewChannelRepository(db) channelService := service.NewChannelService(channelRepository, groupRepository, apiKeyAuthCacheInvalidator) - availableChannelHandler := admin.NewAvailableChannelHandler(channelService) modelPricingResolver := service.NewModelPricingResolver(channelService, billingService) balanceNotifyService := service.ProvideBalanceNotifyService(emailService, settingRepository, accountRepository) 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) @@ -236,7 +235,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { paymentOrderExpiryService := service.ProvidePaymentOrderExpiryService(paymentService) paymentHandler := admin.NewPaymentHandler(paymentService, paymentConfigService) availableChannelUserHandler := handler.NewAvailableChannelHandler(channelService, apiKeyService) - adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, availableChannelHandler, paymentHandler) + adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, paymentHandler) usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig) userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient) userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig) diff --git a/backend/internal/handler/admin/available_channel_handler.go b/backend/internal/handler/admin/available_channel_handler.go deleted file mode 100644 index 45b8f357..00000000 --- a/backend/internal/handler/admin/available_channel_handler.go +++ /dev/null @@ -1,95 +0,0 @@ -package admin - -import ( - "github.com/Wei-Shaw/sub2api/internal/pkg/response" - "github.com/Wei-Shaw/sub2api/internal/service" - - "github.com/gin-gonic/gin" -) - -// AvailableChannelHandler 处理「可用渠道」聚合视图的管理员接口。 -// -// 该视图以只读方式聚合渠道基础信息、关联分组与推导出的支持模型列表(无通配符)。 -type AvailableChannelHandler struct { - channelService *service.ChannelService -} - -// NewAvailableChannelHandler 创建 AvailableChannelHandler 实例。 -func NewAvailableChannelHandler(channelService *service.ChannelService) *AvailableChannelHandler { - return &AvailableChannelHandler{channelService: channelService} -} - -// availableGroupResponse 响应中的分组概要。 -type availableGroupResponse struct { - ID int64 `json:"id"` - Name string `json:"name"` - Platform string `json:"platform"` -} - -// supportedModelResponse 响应中的支持模型条目。 -type supportedModelResponse struct { - Name string `json:"name"` - Platform string `json:"platform"` - Pricing *channelModelPricingResponse `json:"pricing"` -} - -// availableChannelResponse 管理员视图完整字段集。 -type availableChannelResponse struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status string `json:"status"` - BillingModelSource string `json:"billing_model_source"` - RestrictModels bool `json:"restrict_models"` - Groups []availableGroupResponse `json:"groups"` - SupportedModels []supportedModelResponse `json:"supported_models"` -} - -// availableChannelToAdminResponse 将 service 层的 AvailableChannel 转为管理员 DTO。 -// 同 package 内复用;也用于构造测试 fixture。 -func availableChannelToAdminResponse(ch service.AvailableChannel) availableChannelResponse { - groups := make([]availableGroupResponse, 0, len(ch.Groups)) - for _, g := range ch.Groups { - groups = append(groups, availableGroupResponse{ID: g.ID, Name: g.Name, Platform: g.Platform}) - } - models := make([]supportedModelResponse, 0, len(ch.SupportedModels)) - for i := range ch.SupportedModels { - m := ch.SupportedModels[i] - var pricing *channelModelPricingResponse - if m.Pricing != nil { - p := pricingToResponse(m.Pricing) - pricing = &p - } - models = append(models, supportedModelResponse{ - Name: m.Name, - Platform: m.Platform, - Pricing: pricing, - }) - } - return availableChannelResponse{ - ID: ch.ID, - Name: ch.Name, - Description: ch.Description, - Status: ch.Status, - BillingModelSource: ch.BillingModelSource, - RestrictModels: ch.RestrictModels, - Groups: groups, - SupportedModels: models, - } -} - -// List 列出所有可用渠道(管理员视图)。 -// GET /api/v1/admin/channels/available -func (h *AvailableChannelHandler) List(c *gin.Context) { - channels, err := h.channelService.ListAvailable(c.Request.Context()) - if err != nil { - response.ErrorFrom(c, err) - return - } - - out := make([]availableChannelResponse, 0, len(channels)) - for _, ch := range channels { - out = append(out, availableChannelToAdminResponse(ch)) - } - response.Success(c, gin.H{"items": out}) -} diff --git a/backend/internal/handler/admin/available_channel_handler_test.go b/backend/internal/handler/admin/available_channel_handler_test.go deleted file mode 100644 index 7d249383..00000000 --- a/backend/internal/handler/admin/available_channel_handler_test.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build unit - -package admin - -import ( - "encoding/json" - "testing" - - "github.com/Wei-Shaw/sub2api/internal/service" - "github.com/stretchr/testify/require" -) - -func TestAvailableChannelToAdminResponse_IncludesFullDTO(t *testing.T) { - // 管理员视图应包含 id / status / billing_model_source / restrict_models 等 - // 管理字段;mapper 是纯透传,BillingModelSource 的默认回填由 service 层负责。 - input := service.AvailableChannel{ - ID: 42, - Name: "ch", - Description: "d", - Status: service.StatusActive, - BillingModelSource: service.BillingModelSourceChannelMapped, - RestrictModels: true, - Groups: []service.AvailableGroupRef{ - {ID: 1, Name: "g1", Platform: "anthropic"}, - }, - SupportedModels: []service.SupportedModel{ - {Name: "claude-sonnet-4-6", Platform: "anthropic"}, - }, - } - - resp := availableChannelToAdminResponse(input) - require.Equal(t, int64(42), resp.ID) - require.Equal(t, "ch", resp.Name) - require.Equal(t, service.StatusActive, resp.Status) - require.Equal(t, service.BillingModelSourceChannelMapped, resp.BillingModelSource) - require.True(t, resp.RestrictModels) - require.Len(t, resp.Groups, 1) - require.Len(t, resp.SupportedModels, 1) - - // JSON 层验证管理字段确实会被序列化。 - raw, err := json.Marshal(resp) - require.NoError(t, err) - var decoded map[string]any - require.NoError(t, json.Unmarshal(raw, &decoded)) - for _, key := range []string{"id", "status", "billing_model_source", "restrict_models", "groups", "supported_models"} { - _, exists := decoded[key] - require.Truef(t, exists, "admin DTO must expose %q", key) - } -} - -func TestAvailableChannelToAdminResponse_PreservesExplicitBillingSource(t *testing.T) { - input := service.AvailableChannel{ - BillingModelSource: service.BillingModelSourceUpstream, - } - resp := availableChannelToAdminResponse(input) - require.Equal(t, service.BillingModelSourceUpstream, resp.BillingModelSource) -} diff --git a/backend/internal/handler/handler.go b/backend/internal/handler/handler.go index a35d8041..aee9d927 100644 --- a/backend/internal/handler/handler.go +++ b/backend/internal/handler/handler.go @@ -33,7 +33,6 @@ type AdminHandlers struct { Channel *admin.ChannelHandler ChannelMonitor *admin.ChannelMonitorHandler ChannelMonitorTemplate *admin.ChannelMonitorRequestTemplateHandler - AvailableChannel *admin.AvailableChannelHandler Payment *admin.PaymentHandler } diff --git a/backend/internal/handler/wire.go b/backend/internal/handler/wire.go index c9296b44..6d175488 100644 --- a/backend/internal/handler/wire.go +++ b/backend/internal/handler/wire.go @@ -36,7 +36,6 @@ func ProvideAdminHandlers( channelHandler *admin.ChannelHandler, channelMonitorHandler *admin.ChannelMonitorHandler, channelMonitorTemplateHandler *admin.ChannelMonitorRequestTemplateHandler, - availableChannelHandler *admin.AvailableChannelHandler, paymentHandler *admin.PaymentHandler, ) *AdminHandlers { return &AdminHandlers{ @@ -67,7 +66,6 @@ func ProvideAdminHandlers( Channel: channelHandler, ChannelMonitor: channelMonitorHandler, ChannelMonitorTemplate: channelMonitorTemplateHandler, - AvailableChannel: availableChannelHandler, Payment: paymentHandler, } } @@ -170,7 +168,6 @@ var ProviderSet = wire.NewSet( admin.NewChannelHandler, admin.NewChannelMonitorHandler, admin.NewChannelMonitorRequestTemplateHandler, - admin.NewAvailableChannelHandler, admin.NewPaymentHandler, // AdminHandlers and Handlers constructors diff --git a/backend/internal/server/routes/admin.go b/backend/internal/server/routes/admin.go index e4b5c548..4b796d55 100644 --- a/backend/internal/server/routes/admin.go +++ b/backend/internal/server/routes/admin.go @@ -560,7 +560,6 @@ func registerChannelRoutes(admin *gin.RouterGroup, h *handler.Handlers) { channels := admin.Group("/channels") { channels.GET("", h.Admin.Channel.List) - channels.GET("/available", h.Admin.AvailableChannel.List) channels.GET("/model-pricing", h.Admin.Channel.GetModelDefaultPricing) channels.GET("/:id", h.Admin.Channel.GetByID) channels.POST("", h.Admin.Channel.Create) diff --git a/frontend/src/api/admin/channels.ts b/frontend/src/api/admin/channels.ts index 7ad4af28..9d430134 100644 --- a/frontend/src/api/admin/channels.ts +++ b/frontend/src/api/admin/channels.ts @@ -164,42 +164,5 @@ export async function getModelDefaultPricing(model: string): Promise { - const { data } = await apiClient.get('/admin/channels/available', { - signal: options?.signal - }) - return data.items -} - -const channelsAPI = { list, getById, create, update, remove, getModelDefaultPricing, listAvailable } +const channelsAPI = { list, getById, create, update, remove, getModelDefaultPricing } export default channelsAPI diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 567876b6..dc886b23 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -370,18 +370,6 @@ const routes: RouteRecordRaw[] = [ descriptionKey: 'admin.groups.description' } }, - { - path: '/admin/available-channels', - name: 'AdminAvailableChannels', - component: () => import('@/views/admin/AvailableChannelsView.vue'), - meta: { - requiresAuth: true, - requiresAdmin: true, - title: 'Available Channels', - titleKey: 'admin.availableChannels.title', - descriptionKey: 'admin.availableChannels.description' - } - }, { path: '/admin/channels', redirect: '/admin/channels/pricing' diff --git a/frontend/src/views/admin/AvailableChannelsView.vue b/frontend/src/views/admin/AvailableChannelsView.vue deleted file mode 100644 index a9b2462f..00000000 --- a/frontend/src/views/admin/AvailableChannelsView.vue +++ /dev/null @@ -1,164 +0,0 @@ - - -