Files
2026-05-30 10:54:32 +08:00

209 lines
7.3 KiB
Go

package app
import (
"context"
"fmt"
"net/http"
"strings"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
type PortalLogicalGroupInfo struct {
LogicalGroupID string `json:"logical_group_id"`
DisplayName string `json:"display_name"`
Description string `json:"description,omitempty"`
UsageScenario string `json:"usage_scenario,omitempty"`
Recommendation string `json:"recommendation,omitempty"`
NextStepHint string `json:"next_step_hint,omitempty"`
VisibilityScope string `json:"visibility_scope,omitempty"`
PackageTier string `json:"package_tier,omitempty"`
PurchaseCTALabel string `json:"purchase_cta_label,omitempty"`
PurchaseCTAURL string `json:"purchase_cta_url,omitempty"`
Status string `json:"status"`
StickyMode string `json:"sticky_mode,omitempty"`
RoutePolicy string `json:"route_policy,omitempty"`
PublicModels []PortalLogicalGroupModel `json:"public_models,omitempty"`
RouteCount int `json:"route_count"`
ActiveRouteCount int `json:"active_route_count"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type PortalLogicalGroupModel struct {
PublicModel string `json:"public_model"`
Status string `json:"status,omitempty"`
}
func handleListPortalLogicalGroups(w http.ResponseWriter, r *http.Request, fn func(context.Context) ([]PortalLogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-portal-logical-groups action is not configured"})
return
}
groups, err := fn(r.Context())
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"logical_groups": groups})
}
func handleGetPortalLogicalGroup(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) (PortalLogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "get-portal-logical-group action is not configured"})
return
}
group, err := fn(r.Context(), strings.TrimSpace(r.PathValue("groupID")))
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"logical_group": group})
}
func handleListPortalLogicalGroupModels(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) ([]PortalLogicalGroupModel, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-portal-logical-group-models action is not configured"})
return
}
models, err := fn(r.Context(), strings.TrimSpace(r.PathValue("groupID")))
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"models": models})
}
func buildListPortalLogicalGroupsAction(sqliteDSN string) func(context.Context) ([]PortalLogicalGroupInfo, error) {
return func(ctx context.Context) ([]PortalLogicalGroupInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return nil, err
}
defer store.Close()
rows, err := store.LogicalGroups().List(ctx)
if err != nil {
return nil, err
}
items := make([]PortalLogicalGroupInfo, 0, len(rows))
for _, row := range rows {
if !isPortalLogicalGroupVisible(row.Status) {
continue
}
item, buildErr := buildPortalLogicalGroupInfo(ctx, store, row)
if buildErr != nil {
return nil, buildErr
}
items = append(items, item)
}
return items, nil
}
}
func buildGetPortalLogicalGroupAction(sqliteDSN string) func(context.Context, string) (PortalLogicalGroupInfo, error) {
return func(ctx context.Context, logicalGroupID string) (PortalLogicalGroupInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return PortalLogicalGroupInfo{}, err
}
defer store.Close()
group, err := getLogicalGroupRow(ctx, store, logicalGroupID)
if err != nil {
return PortalLogicalGroupInfo{}, err
}
if !isPortalLogicalGroupVisible(group.Status) {
return PortalLogicalGroupInfo{}, fmt.Errorf("logical group %q not found", strings.TrimSpace(logicalGroupID))
}
return buildPortalLogicalGroupInfo(ctx, store, group)
}
}
func buildListPortalLogicalGroupModelsAction(sqliteDSN string) func(context.Context, string) ([]PortalLogicalGroupModel, error) {
return func(ctx context.Context, logicalGroupID string) ([]PortalLogicalGroupModel, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return nil, err
}
defer store.Close()
group, err := getLogicalGroupRow(ctx, store, logicalGroupID)
if err != nil {
return nil, err
}
if !isPortalLogicalGroupVisible(group.Status) {
return nil, fmt.Errorf("logical group %q not found", strings.TrimSpace(logicalGroupID))
}
rows, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, group.LogicalGroupID)
if err != nil {
return nil, err
}
return portalLogicalGroupModelsFromRows(rows), nil
}
}
func buildPortalLogicalGroupInfo(ctx context.Context, store *sqlite.DB, group sqlite.LogicalGroup) (PortalLogicalGroupInfo, error) {
modelRows, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, group.LogicalGroupID)
if err != nil {
return PortalLogicalGroupInfo{}, err
}
routeRows, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, group.LogicalGroupID)
if err != nil {
return PortalLogicalGroupInfo{}, err
}
routeCount := 0
activeRouteCount := 0
for _, route := range routeRows {
routeCount++
if strings.EqualFold(strings.TrimSpace(route.Status), "active") {
activeRouteCount++
}
}
return PortalLogicalGroupInfo{
LogicalGroupID: group.LogicalGroupID,
DisplayName: group.DisplayName,
Description: group.Description,
UsageScenario: group.UsageScenario,
Recommendation: group.Recommendation,
NextStepHint: group.NextStepHint,
VisibilityScope: group.VisibilityScope,
PackageTier: group.PackageTier,
PurchaseCTALabel: group.PurchaseCTALabel,
PurchaseCTAURL: group.PurchaseCTAURL,
Status: group.Status,
StickyMode: group.StickyMode,
RoutePolicy: group.RoutePolicy,
PublicModels: portalLogicalGroupModelsFromRows(modelRows),
RouteCount: routeCount,
ActiveRouteCount: activeRouteCount,
CreatedAt: group.CreatedAt,
UpdatedAt: group.UpdatedAt,
}, nil
}
func portalLogicalGroupModelsFromRows(rows []sqlite.LogicalGroupModel) []PortalLogicalGroupModel {
result := make([]PortalLogicalGroupModel, 0, len(rows))
for _, row := range rows {
if !isPortalLogicalGroupModelVisible(row.Status) {
continue
}
result = append(result, PortalLogicalGroupModel{
PublicModel: row.PublicModel,
Status: row.Status,
})
}
return result
}
func isPortalLogicalGroupVisible(status string) bool {
return strings.EqualFold(strings.TrimSpace(status), "active")
}
func isPortalLogicalGroupModelVisible(status string) bool {
status = strings.TrimSpace(status)
return status == "" || strings.EqualFold(status, "active")
}