Follow-up to the available-channels review pass. No behavior change for end users; tightens internals based on three independent code reviews. Backend - service/channel.go: collapse buildPricingLookup + pricedNamesFor into a single platformPricingIndex (byLower + originalCase + ordered names), built once per SupportedModels call. Fixes a casing- consistency bug where the same logical model appeared with mapping case in the exact branch but pricing case in the wildcard branch — pricing's original case now wins everywhere. - service/channel.go: doc that a mapping key of just "*" expands to every priced model on the platform (intentional "passthrough all"). - service/channel_available.go: normalize empty BillingModelSource to channel_mapped at construction time, removing the same fallback duplicated in the admin DTO mapper and the admin Vue template. - handler/admin/available_channel_handler.go: unexport availableChannelToAdminResponse (same-package usage only); mapper is now a pure passthrough. - handler/available_channel_handler.go: drop the middleware2 alias (no name collision in this file). Frontend - utils/pricing.ts: extract formatScaled, used by SupportedModelChip and PricingRow. - api/admin/channels.ts: re-export BillingMode from constants/channel; tighten Channel.status / billing_model_source to ChannelStatus / BillingModelSource (and same for AvailableChannel). - components/channels/AvailableChannelsTable.vue: drop dead withDefaults wrapper (loading is required, both call sites pass it). - views/admin/AvailableChannelsView.vue: drop the redundant || BILLING_MODEL_SOURCE_CHANNEL_MAPPED fallback (now applied in service layer); remove unused import. - i18n zh + en: delete unused tierLabel and tokenRange keys from both availableChannels.pricing and admin.availableChannels.pricing. Tests - New: SupportedModels_ExactKeyUsesPricedCaseWhenAvailable locks the pricing-case-wins rule. - New: SupportedModels_AsteriskOnlyMappingExpandsAllPriced documents the "*" expansion rule. - Admin handler: existing tests adjusted to pass an explicit BillingModelSource (default-fill is now exercised by service tests).
90 lines
2.5 KiB
Go
90 lines
2.5 KiB
Go
package service
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"sort"
|
||
"strings"
|
||
)
|
||
|
||
// AvailableGroupRef 渠道视图中关联分组的简要信息。
|
||
type AvailableGroupRef struct {
|
||
ID int64
|
||
Name string
|
||
Platform string
|
||
}
|
||
|
||
// AvailableChannel 可用渠道视图:用于「可用渠道」页面展示渠道基础信息 +
|
||
// 关联的分组 + 推导出的支持模型列表(无通配符)。
|
||
type AvailableChannel struct {
|
||
ID int64
|
||
Name string
|
||
Description string
|
||
Status string
|
||
BillingModelSource string
|
||
RestrictModels bool
|
||
Groups []AvailableGroupRef
|
||
SupportedModels []SupportedModel
|
||
}
|
||
|
||
// ListAvailable 返回所有渠道的可用视图:每个渠道附带关联分组信息与支持模型列表。
|
||
//
|
||
// 支持模型通过 (*Channel).SupportedModels() 计算得到(见 channel.go)。
|
||
// 关联分组信息通过 groupRepo.ListActive 查询后按 ID 映射;渠道 GroupIDs 中未在活跃列表中
|
||
// 的分组(已停用或删除)会被忽略。
|
||
func (s *ChannelService) ListAvailable(ctx context.Context) ([]AvailableChannel, error) {
|
||
channels, err := s.repo.ListAll(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("list channels: %w", err)
|
||
}
|
||
|
||
groupByID := make(map[int64]AvailableGroupRef)
|
||
if s.groupRepo != nil {
|
||
groups, err := s.groupRepo.ListActive(ctx)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("list active groups: %w", err)
|
||
}
|
||
for i := range groups {
|
||
g := groups[i]
|
||
groupByID[g.ID] = AvailableGroupRef{
|
||
ID: g.ID,
|
||
Name: g.Name,
|
||
Platform: g.Platform,
|
||
}
|
||
}
|
||
}
|
||
|
||
out := make([]AvailableChannel, 0, len(channels))
|
||
for i := range channels {
|
||
ch := &channels[i]
|
||
groups := make([]AvailableGroupRef, 0, len(ch.GroupIDs))
|
||
for _, gid := range ch.GroupIDs {
|
||
if ref, ok := groupByID[gid]; ok {
|
||
groups = append(groups, ref)
|
||
}
|
||
}
|
||
sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name })
|
||
|
||
billingSource := ch.BillingModelSource
|
||
if billingSource == "" {
|
||
billingSource = BillingModelSourceChannelMapped
|
||
}
|
||
|
||
out = append(out, AvailableChannel{
|
||
ID: ch.ID,
|
||
Name: ch.Name,
|
||
Description: ch.Description,
|
||
Status: ch.Status,
|
||
BillingModelSource: billingSource,
|
||
RestrictModels: ch.RestrictModels,
|
||
Groups: groups,
|
||
SupportedModels: ch.SupportedModels(),
|
||
})
|
||
}
|
||
|
||
sort.SliceStable(out, func(i, j int) bool {
|
||
return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name)
|
||
})
|
||
return out, nil
|
||
}
|