Files
sub2api-cn-relay-manager/internal/app/logical_groups_api.go
2026-05-30 10:54:32 +08:00

883 lines
34 KiB
Go

package app
import (
"context"
"database/sql"
"errors"
"fmt"
"net/http"
"strings"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
type CreateLogicalGroupRequest struct {
LogicalGroupID string `json:"logical_group_id"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
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"`
RoutePolicy string `json:"route_policy,omitempty"`
StickyMode string `json:"sticky_mode,omitempty"`
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
UserModelTTLSeconds int `json:"user_model_ttl_seconds,omitempty"`
FailoverThreshold int `json:"failover_threshold,omitempty"`
CooldownSeconds int `json:"cooldown_seconds,omitempty"`
}
type UpdateLogicalGroupRequest struct {
LogicalGroupID string `json:"-"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
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"`
RoutePolicy string `json:"route_policy,omitempty"`
StickyMode string `json:"sticky_mode,omitempty"`
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
UserModelTTLSeconds int `json:"user_model_ttl_seconds,omitempty"`
FailoverThreshold int `json:"failover_threshold,omitempty"`
CooldownSeconds int `json:"cooldown_seconds,omitempty"`
}
type LogicalGroupInfo struct {
LogicalGroupID string `json:"logical_group_id"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
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"`
RoutePolicy string `json:"route_policy,omitempty"`
StickyMode string `json:"sticky_mode,omitempty"`
ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"`
UserModelTTLSeconds int `json:"user_model_ttl_seconds,omitempty"`
FailoverThreshold int `json:"failover_threshold,omitempty"`
CooldownSeconds int `json:"cooldown_seconds,omitempty"`
Models []LogicalGroupModelInfo `json:"models,omitempty"`
Routes []LogicalGroupRouteInfo `json:"routes,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type CreateLogicalGroupModelRequest struct {
LogicalGroupID string `json:"-"`
PublicModel string `json:"public_model"`
Status string `json:"status,omitempty"`
}
type DeleteLogicalGroupModelRequest struct {
LogicalGroupID string
PublicModel string
}
type LogicalGroupModelInfo struct {
PublicModel string `json:"public_model"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type CreateLogicalGroupRouteRequest struct {
LogicalGroupID string `json:"-"`
RouteID string `json:"route_id"`
Name string `json:"name"`
Status string `json:"status"`
Priority int `json:"priority"`
Weight int `json:"weight,omitempty"`
ShadowGroupID string `json:"shadow_group_id"`
ShadowHostID string `json:"shadow_host_id"`
UpstreamBaseURLHint string `json:"upstream_base_url_hint,omitempty"`
CooldownUntil string `json:"cooldown_until,omitempty"`
}
type UpdateLogicalGroupRouteRequest struct {
LogicalGroupID string `json:"-"`
RouteID string `json:"-"`
Name string `json:"name"`
Status string `json:"status"`
Priority int `json:"priority"`
Weight int `json:"weight,omitempty"`
ShadowGroupID string `json:"shadow_group_id"`
ShadowHostID string `json:"shadow_host_id"`
UpstreamBaseURLHint string `json:"upstream_base_url_hint,omitempty"`
CooldownUntil string `json:"cooldown_until,omitempty"`
}
type DeleteLogicalGroupRouteRequest struct {
LogicalGroupID string
RouteID string
}
type LogicalGroupRouteInfo struct {
RouteID string `json:"route_id"`
LogicalGroupID string `json:"logical_group_id"`
Name string `json:"name"`
Status string `json:"status"`
Priority int `json:"priority"`
Weight int `json:"weight,omitempty"`
ShadowGroupID string `json:"shadow_group_id"`
ShadowHostID string `json:"shadow_host_id"`
UpstreamBaseURLHint string `json:"upstream_base_url_hint,omitempty"`
CooldownUntil string `json:"cooldown_until,omitempty"`
Models []LogicalGroupRouteModelInfo `json:"models,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
type CreateLogicalGroupRouteModelRequest struct {
LogicalGroupID string `json:"-"`
RouteID string `json:"-"`
PublicModel string `json:"public_model"`
ShadowModel string `json:"shadow_model,omitempty"`
Status string `json:"status,omitempty"`
}
type ListLogicalGroupRouteModelsRequest struct {
LogicalGroupID string
RouteID string
}
type LogicalGroupRouteModelInfo struct {
PublicModel string `json:"public_model"`
ShadowModel string `json:"shadow_model,omitempty"`
Status string `json:"status,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
func handleCreateLogicalGroup(w http.ResponseWriter, r *http.Request, fn func(context.Context, CreateLogicalGroupRequest) (LogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "create-logical-group action is not configured"})
return
}
var req CreateLogicalGroupRequest
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
group, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusCreated, map[string]any{"logical_group": group})
}
func handleListLogicalGroups(w http.ResponseWriter, r *http.Request, fn func(context.Context) ([]LogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-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 handleGetLogicalGroup(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) (LogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "get-logical-group action is not configured"})
return
}
groupID := strings.TrimSpace(r.PathValue("groupID"))
group, err := fn(r.Context(), groupID)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"logical_group": group})
}
func handleUpdateLogicalGroup(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateLogicalGroupRequest) (LogicalGroupInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "update-logical-group action is not configured"})
return
}
var req UpdateLogicalGroupRequest
req.LogicalGroupID = strings.TrimSpace(r.PathValue("groupID"))
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
group, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"logical_group": group})
}
func handleDeleteLogicalGroup(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) error) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "delete-logical-group action is not configured"})
return
}
if err := fn(r.Context(), strings.TrimSpace(r.PathValue("groupID"))); err != nil {
writeHTTPError(w, classifyError(err))
return
}
w.WriteHeader(http.StatusNoContent)
}
func handleCreateLogicalGroupModel(w http.ResponseWriter, r *http.Request, fn func(context.Context, CreateLogicalGroupModelRequest) (LogicalGroupModelInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "create-logical-group-model action is not configured"})
return
}
var req CreateLogicalGroupModelRequest
req.LogicalGroupID = strings.TrimSpace(r.PathValue("groupID"))
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
model, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusCreated, map[string]any{"logical_group_model": model})
}
func handleListLogicalGroupModels(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) ([]LogicalGroupModelInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-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 handleDeleteLogicalGroupModel(w http.ResponseWriter, r *http.Request, fn func(context.Context, DeleteLogicalGroupModelRequest) error) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "delete-logical-group-model action is not configured"})
return
}
if err := fn(r.Context(), DeleteLogicalGroupModelRequest{
LogicalGroupID: strings.TrimSpace(r.PathValue("groupID")),
PublicModel: strings.TrimSpace(r.PathValue("model")),
}); err != nil {
writeHTTPError(w, classifyError(err))
return
}
w.WriteHeader(http.StatusNoContent)
}
func handleCreateLogicalGroupRoute(w http.ResponseWriter, r *http.Request, fn func(context.Context, CreateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "create-logical-group-route action is not configured"})
return
}
var req CreateLogicalGroupRouteRequest
req.LogicalGroupID = strings.TrimSpace(r.PathValue("groupID"))
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
route, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusCreated, map[string]any{"route": route})
}
func handleListLogicalGroupRoutes(w http.ResponseWriter, r *http.Request, fn func(context.Context, string) ([]LogicalGroupRouteInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-logical-group-routes action is not configured"})
return
}
routes, err := fn(r.Context(), strings.TrimSpace(r.PathValue("groupID")))
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"routes": routes})
}
func handleUpdateLogicalGroupRoute(w http.ResponseWriter, r *http.Request, fn func(context.Context, UpdateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "update-logical-group-route action is not configured"})
return
}
var req UpdateLogicalGroupRouteRequest
req.LogicalGroupID = strings.TrimSpace(r.PathValue("groupID"))
req.RouteID = strings.TrimSpace(r.PathValue("routeID"))
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
route, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"route": route})
}
func handleDeleteLogicalGroupRoute(w http.ResponseWriter, r *http.Request, fn func(context.Context, DeleteLogicalGroupRouteRequest) error) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "delete-logical-group-route action is not configured"})
return
}
if err := fn(r.Context(), DeleteLogicalGroupRouteRequest{
LogicalGroupID: strings.TrimSpace(r.PathValue("groupID")),
RouteID: strings.TrimSpace(r.PathValue("routeID")),
}); err != nil {
writeHTTPError(w, classifyError(err))
return
}
w.WriteHeader(http.StatusNoContent)
}
func handleCreateLogicalGroupRouteModel(w http.ResponseWriter, r *http.Request, fn func(context.Context, CreateLogicalGroupRouteModelRequest) (LogicalGroupRouteModelInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "create-logical-group-route-model action is not configured"})
return
}
var req CreateLogicalGroupRouteModelRequest
req.LogicalGroupID = strings.TrimSpace(r.PathValue("groupID"))
req.RouteID = strings.TrimSpace(r.PathValue("routeID"))
if err := decodeJSON(r, &req); err != nil {
writeHTTPError(w, err)
return
}
model, err := fn(r.Context(), req)
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusCreated, map[string]any{"route_model": model})
}
func handleListLogicalGroupRouteModels(w http.ResponseWriter, r *http.Request, fn func(context.Context, ListLogicalGroupRouteModelsRequest) ([]LogicalGroupRouteModelInfo, error)) {
if fn == nil {
writeHTTPError(w, &httpError{StatusCode: http.StatusInternalServerError, Code: "server_misconfigured", Message: "list-logical-group-route-models action is not configured"})
return
}
models, err := fn(r.Context(), ListLogicalGroupRouteModelsRequest{
LogicalGroupID: strings.TrimSpace(r.PathValue("groupID")),
RouteID: strings.TrimSpace(r.PathValue("routeID")),
})
if err != nil {
writeHTTPError(w, classifyError(err))
return
}
writeJSON(w, http.StatusOK, map[string]any{"route_models": models})
}
func buildCreateLogicalGroupAction(sqliteDSN string) func(context.Context, CreateLogicalGroupRequest) (LogicalGroupInfo, error) {
return func(ctx context.Context, req CreateLogicalGroupRequest) (LogicalGroupInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupInfo{}, err
}
defer store.Close()
row := logicalGroupRequestToRow(req)
if _, err := store.LogicalGroups().Create(ctx, row); err != nil {
return LogicalGroupInfo{}, err
}
return loadLogicalGroupInfo(ctx, store, row.LogicalGroupID)
}
}
func buildListLogicalGroupsAction(sqliteDSN string) func(context.Context) ([]LogicalGroupInfo, error) {
return func(ctx context.Context) ([]LogicalGroupInfo, 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
}
result := make([]LogicalGroupInfo, 0, len(rows))
for _, row := range rows {
info, err := loadLogicalGroupInfo(ctx, store, row.LogicalGroupID)
if err != nil {
return nil, err
}
result = append(result, info)
}
return result, nil
}
}
func buildGetLogicalGroupAction(sqliteDSN string) func(context.Context, string) (LogicalGroupInfo, error) {
return func(ctx context.Context, logicalGroupID string) (LogicalGroupInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupInfo{}, err
}
defer store.Close()
return loadLogicalGroupInfo(ctx, store, logicalGroupID)
}
}
func buildUpdateLogicalGroupAction(sqliteDSN string) func(context.Context, UpdateLogicalGroupRequest) (LogicalGroupInfo, error) {
return func(ctx context.Context, req UpdateLogicalGroupRequest) (LogicalGroupInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupInfo{}, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, req.LogicalGroupID); err != nil {
return LogicalGroupInfo{}, err
}
if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, logicalGroupRequestToRow(CreateLogicalGroupRequest{
LogicalGroupID: req.LogicalGroupID,
DisplayName: req.DisplayName,
Status: req.Status,
Description: req.Description,
UsageScenario: req.UsageScenario,
Recommendation: req.Recommendation,
NextStepHint: req.NextStepHint,
VisibilityScope: req.VisibilityScope,
PackageTier: req.PackageTier,
PurchaseCTALabel: req.PurchaseCTALabel,
PurchaseCTAURL: req.PurchaseCTAURL,
RoutePolicy: req.RoutePolicy,
StickyMode: req.StickyMode,
ConversationTTLSeconds: req.ConversationTTLSeconds,
UserModelTTLSeconds: req.UserModelTTLSeconds,
FailoverThreshold: req.FailoverThreshold,
CooldownSeconds: req.CooldownSeconds,
})); err != nil {
return LogicalGroupInfo{}, err
}
return loadLogicalGroupInfo(ctx, store, req.LogicalGroupID)
}
}
func buildDeleteLogicalGroupAction(sqliteDSN string) func(context.Context, string) error {
return func(ctx context.Context, logicalGroupID string) error {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return err
}
defer store.Close()
return store.LogicalGroups().DeleteByLogicalGroupID(ctx, strings.TrimSpace(logicalGroupID))
}
}
func buildCreateLogicalGroupModelAction(sqliteDSN string) func(context.Context, CreateLogicalGroupModelRequest) (LogicalGroupModelInfo, error) {
return func(ctx context.Context, req CreateLogicalGroupModelRequest) (LogicalGroupModelInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupModelInfo{}, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, req.LogicalGroupID); err != nil {
return LogicalGroupModelInfo{}, err
}
row := sqlite.LogicalGroupModel{
LogicalGroupID: strings.TrimSpace(req.LogicalGroupID),
PublicModel: strings.TrimSpace(req.PublicModel),
Status: strings.TrimSpace(req.Status),
}
if _, err := store.LogicalGroupModels().Create(ctx, row); err != nil {
return LogicalGroupModelInfo{}, err
}
models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, row.LogicalGroupID)
if err != nil {
return LogicalGroupModelInfo{}, err
}
for _, model := range models {
if model.PublicModel == row.PublicModel {
return logicalGroupModelRowToInfo(model), nil
}
}
return LogicalGroupModelInfo{}, fmt.Errorf("logical group model %q/%q not found", row.LogicalGroupID, row.PublicModel)
}
}
func buildListLogicalGroupModelsAction(sqliteDSN string) func(context.Context, string) ([]LogicalGroupModelInfo, error) {
return func(ctx context.Context, logicalGroupID string) ([]LogicalGroupModelInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return nil, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, logicalGroupID); err != nil {
return nil, err
}
rows, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, logicalGroupID)
if err != nil {
return nil, err
}
return logicalGroupModelRowsToInfo(rows), nil
}
}
func buildDeleteLogicalGroupModelAction(sqliteDSN string) func(context.Context, DeleteLogicalGroupModelRequest) error {
return func(ctx context.Context, req DeleteLogicalGroupModelRequest) error {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, req.LogicalGroupID); err != nil {
return err
}
return store.LogicalGroupModels().DeleteByLogicalGroupIDAndModel(ctx, strings.TrimSpace(req.LogicalGroupID), strings.TrimSpace(req.PublicModel))
}
}
func buildCreateLogicalGroupRouteAction(sqliteDSN string) func(context.Context, CreateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
return func(ctx context.Context, req CreateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupRouteInfo{}, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, req.LogicalGroupID); err != nil {
return LogicalGroupRouteInfo{}, err
}
row := logicalGroupRouteRequestToRow(req)
if _, err := store.LogicalGroupRoutes().Create(ctx, row); err != nil {
return LogicalGroupRouteInfo{}, err
}
return loadLogicalGroupRouteInfo(ctx, store, row.RouteID)
}
}
func buildListLogicalGroupRoutesAction(sqliteDSN string) func(context.Context, string) ([]LogicalGroupRouteInfo, error) {
return func(ctx context.Context, logicalGroupID string) ([]LogicalGroupRouteInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return nil, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, logicalGroupID); err != nil {
return nil, err
}
rows, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, logicalGroupID)
if err != nil {
return nil, err
}
return logicalGroupRouteRowsToInfo(ctx, store, rows)
}
}
func buildUpdateLogicalGroupRouteAction(sqliteDSN string) func(context.Context, UpdateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
return func(ctx context.Context, req UpdateLogicalGroupRouteRequest) (LogicalGroupRouteInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupRouteInfo{}, err
}
defer store.Close()
if _, err := getLogicalGroupRow(ctx, store, req.LogicalGroupID); err != nil {
return LogicalGroupRouteInfo{}, err
}
existing, err := getLogicalGroupRouteRow(ctx, store, req.RouteID)
if err != nil {
return LogicalGroupRouteInfo{}, err
}
if existing.LogicalGroupID != strings.TrimSpace(req.LogicalGroupID) {
return LogicalGroupRouteInfo{}, fmt.Errorf("logical group route %q not found under logical group %q", req.RouteID, req.LogicalGroupID)
}
row := logicalGroupRouteRequestToRow(CreateLogicalGroupRouteRequest{
LogicalGroupID: req.LogicalGroupID,
RouteID: req.RouteID,
Name: req.Name,
Status: req.Status,
Priority: req.Priority,
Weight: req.Weight,
ShadowGroupID: req.ShadowGroupID,
ShadowHostID: req.ShadowHostID,
UpstreamBaseURLHint: req.UpstreamBaseURLHint,
CooldownUntil: req.CooldownUntil,
})
if err := store.LogicalGroupRoutes().UpdateByRouteID(ctx, row); err != nil {
return LogicalGroupRouteInfo{}, err
}
return loadLogicalGroupRouteInfo(ctx, store, req.RouteID)
}
}
func buildDeleteLogicalGroupRouteAction(sqliteDSN string) func(context.Context, DeleteLogicalGroupRouteRequest) error {
return func(ctx context.Context, req DeleteLogicalGroupRouteRequest) error {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return err
}
defer store.Close()
existing, err := getLogicalGroupRouteRow(ctx, store, req.RouteID)
if err != nil {
return err
}
if existing.LogicalGroupID != strings.TrimSpace(req.LogicalGroupID) {
return fmt.Errorf("logical group route %q not found under logical group %q", req.RouteID, req.LogicalGroupID)
}
return store.LogicalGroupRoutes().DeleteByRouteID(ctx, strings.TrimSpace(req.RouteID))
}
}
func buildCreateLogicalGroupRouteModelAction(sqliteDSN string) func(context.Context, CreateLogicalGroupRouteModelRequest) (LogicalGroupRouteModelInfo, error) {
return func(ctx context.Context, req CreateLogicalGroupRouteModelRequest) (LogicalGroupRouteModelInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return LogicalGroupRouteModelInfo{}, err
}
defer store.Close()
route, err := getLogicalGroupRouteRow(ctx, store, req.RouteID)
if err != nil {
return LogicalGroupRouteModelInfo{}, err
}
if route.LogicalGroupID != strings.TrimSpace(req.LogicalGroupID) {
return LogicalGroupRouteModelInfo{}, fmt.Errorf("logical group route %q not found under logical group %q", req.RouteID, req.LogicalGroupID)
}
row := sqlite.LogicalGroupRouteModel{
RouteID: strings.TrimSpace(req.RouteID),
PublicModel: strings.TrimSpace(req.PublicModel),
ShadowModel: strings.TrimSpace(req.ShadowModel),
Status: strings.TrimSpace(req.Status),
}
if _, err := store.LogicalGroupRouteModels().Create(ctx, row); err != nil {
return LogicalGroupRouteModelInfo{}, err
}
models, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, row.RouteID)
if err != nil {
return LogicalGroupRouteModelInfo{}, err
}
for _, model := range models {
if model.PublicModel == row.PublicModel {
return logicalGroupRouteModelRowToInfo(model), nil
}
}
return LogicalGroupRouteModelInfo{}, fmt.Errorf("logical group route model %q/%q not found", row.RouteID, row.PublicModel)
}
}
func buildListLogicalGroupRouteModelsAction(sqliteDSN string) func(context.Context, ListLogicalGroupRouteModelsRequest) ([]LogicalGroupRouteModelInfo, error) {
return func(ctx context.Context, req ListLogicalGroupRouteModelsRequest) ([]LogicalGroupRouteModelInfo, error) {
store, err := sqlite.Open(ctx, sqliteDSN)
if err != nil {
return nil, err
}
defer store.Close()
route, err := getLogicalGroupRouteRow(ctx, store, req.RouteID)
if err != nil {
return nil, err
}
if route.LogicalGroupID != strings.TrimSpace(req.LogicalGroupID) {
return nil, fmt.Errorf("logical group route %q not found under logical group %q", req.RouteID, req.LogicalGroupID)
}
rows, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, req.RouteID)
if err != nil {
return nil, err
}
return logicalGroupRouteModelRowsToInfo(rows), nil
}
}
func logicalGroupRequestToRow(req CreateLogicalGroupRequest) sqlite.LogicalGroup {
return sqlite.LogicalGroup{
LogicalGroupID: strings.TrimSpace(req.LogicalGroupID),
DisplayName: strings.TrimSpace(req.DisplayName),
Status: strings.TrimSpace(req.Status),
Description: strings.TrimSpace(req.Description),
UsageScenario: strings.TrimSpace(req.UsageScenario),
Recommendation: strings.TrimSpace(req.Recommendation),
NextStepHint: strings.TrimSpace(req.NextStepHint),
VisibilityScope: strings.TrimSpace(req.VisibilityScope),
PackageTier: strings.TrimSpace(req.PackageTier),
PurchaseCTALabel: strings.TrimSpace(req.PurchaseCTALabel),
PurchaseCTAURL: strings.TrimSpace(req.PurchaseCTAURL),
RoutePolicy: strings.TrimSpace(req.RoutePolicy),
StickyMode: strings.TrimSpace(req.StickyMode),
ConversationTTLSeconds: req.ConversationTTLSeconds,
UserModelTTLSeconds: req.UserModelTTLSeconds,
FailoverThreshold: req.FailoverThreshold,
CooldownSeconds: req.CooldownSeconds,
}
}
func logicalGroupRouteRequestToRow(req CreateLogicalGroupRouteRequest) sqlite.LogicalGroupRoute {
return sqlite.LogicalGroupRoute{
RouteID: strings.TrimSpace(req.RouteID),
LogicalGroupID: strings.TrimSpace(req.LogicalGroupID),
Name: strings.TrimSpace(req.Name),
Status: strings.TrimSpace(req.Status),
Priority: req.Priority,
Weight: req.Weight,
ShadowGroupID: strings.TrimSpace(req.ShadowGroupID),
ShadowHostID: strings.TrimSpace(req.ShadowHostID),
UpstreamBaseURLHint: strings.TrimSpace(req.UpstreamBaseURLHint),
CooldownUntil: strings.TrimSpace(req.CooldownUntil),
}
}
func loadLogicalGroupInfo(ctx context.Context, store *sqlite.DB, logicalGroupID string) (LogicalGroupInfo, error) {
group, err := getLogicalGroupRow(ctx, store, logicalGroupID)
if err != nil {
return LogicalGroupInfo{}, err
}
models, err := store.LogicalGroupModels().ListByLogicalGroupID(ctx, group.LogicalGroupID)
if err != nil {
return LogicalGroupInfo{}, err
}
routes, err := store.LogicalGroupRoutes().ListByLogicalGroupID(ctx, group.LogicalGroupID)
if err != nil {
return LogicalGroupInfo{}, err
}
routeInfos, err := logicalGroupRouteRowsToInfo(ctx, store, routes)
if err != nil {
return LogicalGroupInfo{}, err
}
return logicalGroupRowToInfo(group, logicalGroupModelRowsToInfo(models), routeInfos), nil
}
func loadLogicalGroupRouteInfo(ctx context.Context, store *sqlite.DB, routeID string) (LogicalGroupRouteInfo, error) {
route, err := getLogicalGroupRouteRow(ctx, store, routeID)
if err != nil {
return LogicalGroupRouteInfo{}, err
}
models, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, route.RouteID)
if err != nil {
return LogicalGroupRouteInfo{}, err
}
return logicalGroupRouteRowToInfo(route, logicalGroupRouteModelRowsToInfo(models)), nil
}
func logicalGroupRowToInfo(group sqlite.LogicalGroup, models []LogicalGroupModelInfo, routes []LogicalGroupRouteInfo) LogicalGroupInfo {
return LogicalGroupInfo{
LogicalGroupID: group.LogicalGroupID,
DisplayName: group.DisplayName,
Status: group.Status,
Description: group.Description,
UsageScenario: group.UsageScenario,
Recommendation: group.Recommendation,
NextStepHint: group.NextStepHint,
VisibilityScope: group.VisibilityScope,
PackageTier: group.PackageTier,
PurchaseCTALabel: group.PurchaseCTALabel,
PurchaseCTAURL: group.PurchaseCTAURL,
RoutePolicy: group.RoutePolicy,
StickyMode: group.StickyMode,
ConversationTTLSeconds: group.ConversationTTLSeconds,
UserModelTTLSeconds: group.UserModelTTLSeconds,
FailoverThreshold: group.FailoverThreshold,
CooldownSeconds: group.CooldownSeconds,
Models: models,
Routes: routes,
CreatedAt: group.CreatedAt,
UpdatedAt: group.UpdatedAt,
}
}
func logicalGroupModelRowsToInfo(rows []sqlite.LogicalGroupModel) []LogicalGroupModelInfo {
result := make([]LogicalGroupModelInfo, 0, len(rows))
for _, row := range rows {
result = append(result, logicalGroupModelRowToInfo(row))
}
return result
}
func logicalGroupModelRowToInfo(row sqlite.LogicalGroupModel) LogicalGroupModelInfo {
return LogicalGroupModelInfo{
PublicModel: row.PublicModel,
Status: row.Status,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
func logicalGroupRouteRowsToInfo(ctx context.Context, store *sqlite.DB, rows []sqlite.LogicalGroupRoute) ([]LogicalGroupRouteInfo, error) {
result := make([]LogicalGroupRouteInfo, 0, len(rows))
for _, row := range rows {
models, err := store.LogicalGroupRouteModels().ListByRouteID(ctx, row.RouteID)
if err != nil {
return nil, err
}
result = append(result, logicalGroupRouteRowToInfo(row, logicalGroupRouteModelRowsToInfo(models)))
}
return result, nil
}
func logicalGroupRouteRowToInfo(row sqlite.LogicalGroupRoute, models []LogicalGroupRouteModelInfo) LogicalGroupRouteInfo {
return LogicalGroupRouteInfo{
RouteID: row.RouteID,
LogicalGroupID: row.LogicalGroupID,
Name: row.Name,
Status: row.Status,
Priority: row.Priority,
Weight: row.Weight,
ShadowGroupID: row.ShadowGroupID,
ShadowHostID: row.ShadowHostID,
UpstreamBaseURLHint: row.UpstreamBaseURLHint,
CooldownUntil: row.CooldownUntil,
Models: models,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
func logicalGroupRouteModelRowsToInfo(rows []sqlite.LogicalGroupRouteModel) []LogicalGroupRouteModelInfo {
result := make([]LogicalGroupRouteModelInfo, 0, len(rows))
for _, row := range rows {
result = append(result, logicalGroupRouteModelRowToInfo(row))
}
return result
}
func logicalGroupRouteModelRowToInfo(row sqlite.LogicalGroupRouteModel) LogicalGroupRouteModelInfo {
return LogicalGroupRouteModelInfo{
PublicModel: row.PublicModel,
ShadowModel: row.ShadowModel,
Status: row.Status,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
func getLogicalGroupRow(ctx context.Context, store *sqlite.DB, logicalGroupID string) (sqlite.LogicalGroup, error) {
row, err := store.LogicalGroups().GetByLogicalGroupID(ctx, strings.TrimSpace(logicalGroupID))
if errors.Is(err, sql.ErrNoRows) {
return sqlite.LogicalGroup{}, fmt.Errorf("logical group %q not found", strings.TrimSpace(logicalGroupID))
}
return row, err
}
func getLogicalGroupRouteRow(ctx context.Context, store *sqlite.DB, routeID string) (sqlite.LogicalGroupRoute, error) {
row, err := store.LogicalGroupRoutes().GetByRouteID(ctx, strings.TrimSpace(routeID))
if errors.Is(err, sql.ErrNoRows) {
return sqlite.LogicalGroupRoute{}, fmt.Errorf("logical group route %q not found", strings.TrimSpace(routeID))
}
return row, err
}