Files
sub2api-cn-relay-manager/internal/store/sqlite/logical_group_routes_repo.go
2026-05-29 19:07:01 +08:00

307 lines
8.7 KiB
Go

package sqlite
import (
"context"
"database/sql"
"fmt"
"strings"
)
type LogicalGroupRoute struct {
ID int64
RouteID string
LogicalGroupID string
Name string
Status string
Priority int
Weight int
ShadowGroupID string
ShadowHostID string
UpstreamBaseURLHint string
CooldownUntil string
CreatedAt string
UpdatedAt string
}
type LogicalGroupRoutesRepo struct {
db execQuerier
}
func newLogicalGroupRoutesRepo(db execQuerier) *LogicalGroupRoutesRepo {
return &LogicalGroupRoutesRepo{db: db}
}
func (r *LogicalGroupRoutesRepo) Create(ctx context.Context, route LogicalGroupRoute) (int64, error) {
route, err := normalizeLogicalGroupRoute(route)
if err != nil {
return 0, err
}
result, err := r.db.ExecContext(
ctx,
`INSERT INTO logical_group_routes (
route_id,
logical_group_id,
name,
status,
priority,
weight,
shadow_group_id,
shadow_host_id,
upstream_base_url_hint,
cooldown_until
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
route.RouteID,
route.LogicalGroupID,
route.Name,
route.Status,
route.Priority,
route.Weight,
route.ShadowGroupID,
route.ShadowHostID,
route.UpstreamBaseURLHint,
route.CooldownUntil,
)
if err != nil {
return 0, fmt.Errorf("insert logical group route %q: %w", route.RouteID, err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("read inserted logical group route id for %q: %w", route.RouteID, err)
}
return id, nil
}
func (r *LogicalGroupRoutesRepo) GetByRouteID(ctx context.Context, routeID string) (LogicalGroupRoute, error) {
routeID = strings.TrimSpace(routeID)
if routeID == "" {
return LogicalGroupRoute{}, fmt.Errorf("route_id is required")
}
var route LogicalGroupRoute
if err := r.db.QueryRowContext(
ctx,
`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
FROM logical_group_routes
WHERE route_id = ?`,
routeID,
).Scan(
&route.ID,
&route.RouteID,
&route.LogicalGroupID,
&route.Name,
&route.Status,
&route.Priority,
&route.Weight,
&route.ShadowGroupID,
&route.ShadowHostID,
&route.UpstreamBaseURLHint,
&route.CooldownUntil,
&route.CreatedAt,
&route.UpdatedAt,
); err != nil {
return LogicalGroupRoute{}, err
}
return route, nil
}
func (r *LogicalGroupRoutesRepo) ListByLogicalGroupID(ctx context.Context, logicalGroupID string) ([]LogicalGroupRoute, error) {
logicalGroupID = strings.TrimSpace(logicalGroupID)
if logicalGroupID == "" {
return nil, fmt.Errorf("logical_group_id is required")
}
rows, err := r.db.QueryContext(
ctx,
`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
FROM logical_group_routes
WHERE logical_group_id = ?
ORDER BY priority ASC, id ASC`,
logicalGroupID,
)
if err != nil {
return nil, fmt.Errorf("list logical group routes for %q: %w", logicalGroupID, err)
}
defer rows.Close()
routes := make([]LogicalGroupRoute, 0)
for rows.Next() {
var route LogicalGroupRoute
if err := rows.Scan(
&route.ID,
&route.RouteID,
&route.LogicalGroupID,
&route.Name,
&route.Status,
&route.Priority,
&route.Weight,
&route.ShadowGroupID,
&route.ShadowHostID,
&route.UpstreamBaseURLHint,
&route.CooldownUntil,
&route.CreatedAt,
&route.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan logical group route: %w", err)
}
routes = append(routes, route)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate logical group routes for %q: %w", logicalGroupID, err)
}
return routes, nil
}
func (r *LogicalGroupRoutesRepo) UpdateByRouteID(ctx context.Context, route LogicalGroupRoute) error {
route, err := normalizeLogicalGroupRoute(route)
if err != nil {
return err
}
result, err := r.db.ExecContext(
ctx,
`UPDATE logical_group_routes
SET logical_group_id = ?, name = ?, status = ?, priority = ?, weight = ?, shadow_group_id = ?, shadow_host_id = ?, upstream_base_url_hint = ?, cooldown_until = ?, updated_at = CURRENT_TIMESTAMP
WHERE route_id = ?`,
route.LogicalGroupID,
route.Name,
route.Status,
route.Priority,
route.Weight,
route.ShadowGroupID,
route.ShadowHostID,
route.UpstreamBaseURLHint,
route.CooldownUntil,
route.RouteID,
)
if err != nil {
return fmt.Errorf("update logical group route %q: %w", route.RouteID, err)
}
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("read updated logical group route rows for %q: %w", route.RouteID, err)
}
if affected == 0 {
return fmt.Errorf("logical group route %q not found", route.RouteID)
}
return nil
}
func (r *LogicalGroupRoutesRepo) DeleteByRouteID(ctx context.Context, routeID string) error {
routeID = strings.TrimSpace(routeID)
if routeID == "" {
return fmt.Errorf("route_id is required")
}
result, err := r.db.ExecContext(ctx, `DELETE FROM logical_group_routes WHERE route_id = ?`, routeID)
if err != nil {
return fmt.Errorf("delete logical group route %q: %w", routeID, err)
}
affected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("read deleted logical group route rows for %q: %w", routeID, err)
}
if affected == 0 {
return fmt.Errorf("logical group route %q not found", routeID)
}
return nil
}
func (r *LogicalGroupRoutesRepo) GetByShadowBinding(ctx context.Context, shadowHostID, shadowGroupID string) (LogicalGroupRoute, error) {
routes, err := r.ListByShadowBinding(ctx, shadowHostID, shadowGroupID)
if err != nil {
return LogicalGroupRoute{}, err
}
switch len(routes) {
case 0:
return LogicalGroupRoute{}, sql.ErrNoRows
case 1:
return routes[0], nil
default:
return LogicalGroupRoute{}, fmt.Errorf("multiple logical group routes match shadow binding %q/%q", strings.TrimSpace(shadowHostID), strings.TrimSpace(shadowGroupID))
}
}
func (r *LogicalGroupRoutesRepo) ListByShadowBinding(ctx context.Context, shadowHostID, shadowGroupID string) ([]LogicalGroupRoute, error) {
shadowHostID = strings.TrimSpace(shadowHostID)
shadowGroupID = strings.TrimSpace(shadowGroupID)
if shadowHostID == "" {
return nil, fmt.Errorf("shadow_host_id is required")
}
if shadowGroupID == "" {
return nil, fmt.Errorf("shadow_group_id is required")
}
rows, err := r.db.QueryContext(
ctx,
`SELECT id, route_id, logical_group_id, name, status, priority, weight, shadow_group_id, shadow_host_id, upstream_base_url_hint, cooldown_until, created_at, updated_at
FROM logical_group_routes
WHERE shadow_host_id = ? AND shadow_group_id = ?
ORDER BY priority ASC, id ASC`,
shadowHostID,
shadowGroupID,
)
if err != nil {
return nil, fmt.Errorf("list logical group routes by shadow binding %q/%q: %w", shadowHostID, shadowGroupID, err)
}
defer rows.Close()
routes := make([]LogicalGroupRoute, 0, 4)
for rows.Next() {
var route LogicalGroupRoute
if err := rows.Scan(
&route.ID,
&route.RouteID,
&route.LogicalGroupID,
&route.Name,
&route.Status,
&route.Priority,
&route.Weight,
&route.ShadowGroupID,
&route.ShadowHostID,
&route.UpstreamBaseURLHint,
&route.CooldownUntil,
&route.CreatedAt,
&route.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan logical group route by shadow binding: %w", err)
}
routes = append(routes, route)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate logical group route by shadow binding %q/%q: %w", shadowHostID, shadowGroupID, err)
}
return routes, nil
}
func normalizeLogicalGroupRoute(route LogicalGroupRoute) (LogicalGroupRoute, error) {
route.RouteID = strings.TrimSpace(route.RouteID)
route.LogicalGroupID = strings.TrimSpace(route.LogicalGroupID)
route.Name = strings.TrimSpace(route.Name)
route.Status = strings.TrimSpace(route.Status)
route.ShadowGroupID = strings.TrimSpace(route.ShadowGroupID)
route.ShadowHostID = strings.TrimSpace(route.ShadowHostID)
route.UpstreamBaseURLHint = strings.TrimSpace(route.UpstreamBaseURLHint)
route.CooldownUntil = strings.TrimSpace(route.CooldownUntil)
switch {
case route.RouteID == "":
return LogicalGroupRoute{}, fmt.Errorf("route_id is required")
case route.LogicalGroupID == "":
return LogicalGroupRoute{}, fmt.Errorf("logical_group_id is required")
case route.Name == "":
return LogicalGroupRoute{}, fmt.Errorf("name is required")
case route.Status == "":
return LogicalGroupRoute{}, fmt.Errorf("status is required")
case route.ShadowGroupID == "":
return LogicalGroupRoute{}, fmt.Errorf("shadow_group_id is required")
case route.ShadowHostID == "":
return LogicalGroupRoute{}, fmt.Errorf("shadow_host_id is required")
}
if route.Weight <= 0 {
route.Weight = 100
}
return route, nil
}