feat(portal): add logical group packaging config

This commit is contained in:
phamnazage-jpg
2026-05-30 10:54:32 +08:00
parent aac18e0df6
commit ef33762db5
10 changed files with 300 additions and 41 deletions

View File

@@ -13,6 +13,8 @@ const (
defaultUserModelTTLSeconds = 1800
defaultFailoverThreshold = 2
defaultCooldownSeconds = 600
defaultLogicalGroupVisibility = "public"
defaultLogicalGroupPackageTier = "standard"
)
type LogicalGroup struct {
@@ -24,6 +26,10 @@ type LogicalGroup struct {
UsageScenario string
Recommendation string
NextStepHint string
VisibilityScope string
PackageTier string
PurchaseCTALabel string
PurchaseCTAURL string
RoutePolicy string
StickyMode string
ConversationTTLSeconds int
@@ -58,13 +64,17 @@ func (r *LogicalGroupsRepo) Create(ctx context.Context, group LogicalGroup) (int
usage_scenario,
recommendation,
next_step_hint,
visibility_scope,
package_tier,
purchase_cta_label,
purchase_cta_url,
route_policy,
sticky_mode,
conversation_ttl_seconds,
user_model_ttl_seconds,
failover_threshold,
cooldown_seconds
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
group.LogicalGroupID,
group.DisplayName,
group.Status,
@@ -72,6 +82,10 @@ func (r *LogicalGroupsRepo) Create(ctx context.Context, group LogicalGroup) (int
group.UsageScenario,
group.Recommendation,
group.NextStepHint,
group.VisibilityScope,
group.PackageTier,
group.PurchaseCTALabel,
group.PurchaseCTAURL,
group.RoutePolicy,
group.StickyMode,
group.ConversationTTLSeconds,
@@ -99,7 +113,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
var group LogicalGroup
if err := r.db.QueryRowContext(
ctx,
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, visibility_scope, package_tier, purchase_cta_label, purchase_cta_url, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
FROM logical_groups
WHERE logical_group_id = ?`,
logicalGroupID,
@@ -112,6 +126,10 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
&group.UsageScenario,
&group.Recommendation,
&group.NextStepHint,
&group.VisibilityScope,
&group.PackageTier,
&group.PurchaseCTALabel,
&group.PurchaseCTAURL,
&group.RoutePolicy,
&group.StickyMode,
&group.ConversationTTLSeconds,
@@ -129,7 +147,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou
func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) {
rows, err := r.db.QueryContext(
ctx,
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
`SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, visibility_scope, package_tier, purchase_cta_label, purchase_cta_url, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at
FROM logical_groups
ORDER BY id ASC`,
)
@@ -150,6 +168,10 @@ func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) {
&group.UsageScenario,
&group.Recommendation,
&group.NextStepHint,
&group.VisibilityScope,
&group.PackageTier,
&group.PurchaseCTALabel,
&group.PurchaseCTAURL,
&group.RoutePolicy,
&group.StickyMode,
&group.ConversationTTLSeconds,
@@ -178,7 +200,7 @@ func (r *LogicalGroupsRepo) UpdateByLogicalGroupID(ctx context.Context, group Lo
result, err := r.db.ExecContext(
ctx,
`UPDATE logical_groups
SET display_name = ?, status = ?, description = ?, usage_scenario = ?, recommendation = ?, next_step_hint = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP
SET display_name = ?, status = ?, description = ?, usage_scenario = ?, recommendation = ?, next_step_hint = ?, visibility_scope = ?, package_tier = ?, purchase_cta_label = ?, purchase_cta_url = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP
WHERE logical_group_id = ?`,
group.DisplayName,
group.Status,
@@ -186,6 +208,10 @@ func (r *LogicalGroupsRepo) UpdateByLogicalGroupID(ctx context.Context, group Lo
group.UsageScenario,
group.Recommendation,
group.NextStepHint,
group.VisibilityScope,
group.PackageTier,
group.PurchaseCTALabel,
group.PurchaseCTAURL,
group.RoutePolicy,
group.StickyMode,
group.ConversationTTLSeconds,
@@ -235,6 +261,10 @@ func normalizeLogicalGroup(group LogicalGroup) (LogicalGroup, error) {
group.UsageScenario = strings.TrimSpace(group.UsageScenario)
group.Recommendation = strings.TrimSpace(group.Recommendation)
group.NextStepHint = strings.TrimSpace(group.NextStepHint)
group.VisibilityScope = strings.TrimSpace(group.VisibilityScope)
group.PackageTier = strings.TrimSpace(group.PackageTier)
group.PurchaseCTALabel = strings.TrimSpace(group.PurchaseCTALabel)
group.PurchaseCTAURL = strings.TrimSpace(group.PurchaseCTAURL)
group.RoutePolicy = strings.TrimSpace(group.RoutePolicy)
group.StickyMode = strings.TrimSpace(group.StickyMode)
@@ -250,6 +280,12 @@ func normalizeLogicalGroup(group LogicalGroup) (LogicalGroup, error) {
if group.RoutePolicy == "" {
group.RoutePolicy = defaultLogicalGroupRoutePolicy
}
if group.VisibilityScope == "" {
group.VisibilityScope = defaultLogicalGroupVisibility
}
if group.PackageTier == "" {
group.PackageTier = defaultLogicalGroupPackageTier
}
if group.StickyMode == "" {
group.StickyMode = defaultLogicalGroupStickyMode
}

View File

@@ -12,13 +12,17 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
ctx := context.Background()
id, err := store.LogicalGroups().Create(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
Description: "shared group",
UsageScenario: "适合统一 GPT 产品入口。",
Recommendation: "优先使用 gpt-5.4。",
NextStepHint: "先创建测试 Key。",
LogicalGroupID: "gpt-shared",
DisplayName: "GPT Shared",
Status: "active",
Description: "shared group",
UsageScenario: "适合统一 GPT 产品入口。",
Recommendation: "优先使用 gpt-5.4。",
NextStepHint: "先创建测试 Key。",
VisibilityScope: "login_required",
PackageTier: "pro",
PurchaseCTALabel: "升级到 Pro",
PurchaseCTAURL: "https://sub.tksea.top/portal/upgrade/pro",
})
if err != nil {
t.Fatalf("Create() error = %v", err)
@@ -40,6 +44,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
if group.UsageScenario != "适合统一 GPT 产品入口。" || group.Recommendation != "优先使用 gpt-5.4。" || group.NextStepHint != "先创建测试 Key。" {
t.Fatalf("guidance fields = %+v, want persisted guidance", group)
}
if group.VisibilityScope != "login_required" || group.PackageTier != "pro" || group.PurchaseCTALabel != "升级到 Pro" || group.PurchaseCTAURL != "https://sub.tksea.top/portal/upgrade/pro" {
t.Fatalf("packaging fields = %+v, want persisted packaging", group)
}
if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, LogicalGroup{
LogicalGroupID: "gpt-shared",
@@ -49,6 +56,10 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
UsageScenario: "适合更新后的产品入口。",
Recommendation: "优先做连通性验证。",
NextStepHint: "先确认订阅再调用。",
VisibilityScope: "entitled_only",
PackageTier: "enterprise",
PurchaseCTALabel: "联系销售升级",
PurchaseCTAURL: "https://sub.tksea.top/portal/contact-sales",
RoutePolicy: "priority",
StickyMode: "user_preferred",
ConversationTTLSeconds: 3600,
@@ -69,6 +80,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) {
if updated.UsageScenario != "适合更新后的产品入口。" || updated.Recommendation != "优先做连通性验证。" || updated.NextStepHint != "先确认订阅再调用。" {
t.Fatalf("updated guidance = %+v, want updated guidance fields", updated)
}
if updated.VisibilityScope != "entitled_only" || updated.PackageTier != "enterprise" || updated.PurchaseCTALabel != "联系销售升级" || updated.PurchaseCTAURL != "https://sub.tksea.top/portal/contact-sales" {
t.Fatalf("updated packaging = %+v, want updated packaging fields", updated)
}
if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil {
t.Fatalf("DeleteByLogicalGroupID() error = %v", err)