Files
sub-store/internal/handler/routes.go
T
杨豪 5a34e739bb
Build and Publish Docker Image / build-and-push (pull_request) Successful in 15m54s
feat: HH-773 合集重命名接入 egress geo 缓存、geo 失败降级与每小时补探测
- service: EgressCacheKey 从 handler 下沉复用;MergeCachedEgressGeo 在合集
  重命名前合并缓存 geo 字段(纯缓存读、零网络 I/O);StripEgressGeoFields
  防止 geo 字段泄漏进订阅输出
- filter: geo 检测失败的节点保留 '[别名] 原名'(或纯原名)参与编号/排序,
  不再丢弃
- handler: 每小时 StartEgressRefresher 对启用源补探测缺失/过期的 egress
  缓存(含 TTL 内 error 结果一律跳过);RegisterRoutes 返回 *Deps;
  cmd/server.go 启动定时任务并在 shutdown 时取消
- tests: service 缓存 key 稳定性/合并/管道测试、handler 补探测跳过/TTL/
  过期/取消测试、filter 降级测试;修复 2 个过时测试(mihomo YAML 配置、
  缓存命中路径)
2026-08-28 16:40:11 +08:00

122 lines
4.1 KiB
Go

package handler
import (
"github.com/gofiber/fiber/v3"
"github.com/jmoiron/sqlx"
"github.com/peterqiu0516/sub-store/internal/config"
"github.com/peterqiu0516/sub-store/internal/database"
"github.com/peterqiu0516/sub-store/internal/middleware"
)
// Deps holds all shared dependencies for handlers.
type Deps struct {
Cfg *config.Config
DB *sqlx.DB
SourceRepo *database.SourceRepo
CollectionRepo *database.CollectionRepo
TemplateRepo *database.TemplateRepo
SettingsRepo *database.SettingsRepo
RecycleRepo *database.RecycleRepo
CacheRepo *database.CacheRepo
}
func NewDeps(cfg *config.Config, db *sqlx.DB) *Deps {
return &Deps{
Cfg: cfg,
DB: db,
SourceRepo: database.NewSourceRepo(db),
CollectionRepo: database.NewCollectionRepo(db),
TemplateRepo: database.NewTemplateRepo(db),
SettingsRepo: database.NewSettingsRepo(db),
RecycleRepo: database.NewRecycleRepo(db),
CacheRepo: database.NewCacheRepo(db),
}
}
// RegisterRoutes wires all routes and returns the Deps used, so callers
// (e.g. server startup) can launch background jobs on the same dependencies.
func RegisterRoutes(app *fiber.App, cfg *config.Config, db *sqlx.DB) *Deps {
deps := NewDeps(cfg, db)
// Admin API group — requires admin token
api := app.Group("/api",
middleware.RequireAdmin(cfg.Auth.AdminToken),
middleware.BodyLimit(cfg.Server.BodyLimit),
)
// Basic API
api.Get("/env", deps.HandleEnv)
api.Get("/scripts", deps.HandleScripts)
api.Get("/settings", deps.HandleGetSettings)
api.Patch("/settings", deps.HandleUpdateSettings)
api.Get("/storage", deps.HandleExportStorage)
api.Post("/storage", deps.HandleImportStorage)
// Source CRUD
api.Get("/sources", deps.HandleListSources)
api.Post("/sources", deps.HandleCreateSource)
api.Put("/sources", deps.HandleSortSources)
api.Post("/sort/sources", deps.HandleSortSourcesLegacy)
api.Get("/sources/:name", deps.HandleGetSource)
api.Patch("/sources/:name", deps.HandleUpdateSource)
api.Delete("/sources/:name", deps.HandleDeleteSource)
// Collection CRUD
api.Get("/collections", deps.HandleListCollections)
api.Post("/collections", deps.HandleCreateCollection)
api.Put("/collections", deps.HandleSortCollections)
api.Post("/sort/collections", deps.HandleSortCollectionsLegacy)
api.Get("/collections/:name", deps.HandleGetCollection)
api.Patch("/collections/:name", deps.HandleUpdateCollection)
api.Delete("/collections/:name", deps.HandleDeleteCollection)
// Template CRUD
api.Get("/templates", deps.HandleListTemplates)
api.Post("/templates", deps.HandleCreateTemplate)
api.Get("/templates/:name", deps.HandleGetTemplate)
api.Patch("/templates/:name", deps.HandleUpdateTemplate)
api.Delete("/templates/:name", deps.HandleDeleteTemplate)
// Recycle bin
api.Get("/recycle-bin", deps.HandleListRecycleBin)
api.Delete("/recycle-bin/:id", deps.HandleDeleteRecycleBinEntry)
api.Post("/recycle-bin/:id/restore", deps.HandleRestoreRecycleBinEntry)
// Preview
api.Post("/preview/source", deps.HandlePreviewSource)
api.Post("/preview/collection", deps.HandlePreviewCollection)
// Download links
api.Get("/link/source/:name", deps.HandleLinkSource)
api.Get("/link/collection/:name", deps.HandleLinkCollection)
// Flow info
api.Get("/source/flow/:name", deps.HandleFlowInfo)
// Tool APIs
api.Post("/proxy/parse", deps.HandleProxyParse)
api.Post("/rule/parse", deps.HandleRuleParse)
api.Post("/utils/proxy-uri", deps.HandleProxyURI)
api.Post("/utils/node-info", deps.HandleNodeInfo)
api.Post("/utils/egress-info", deps.HandleEgressInfo)
// Public download routes — no admin token required, uses download token
app.Get("/sources/:name/:token", deps.HandleDownloadSource)
app.Get("/collections/:name/:token", deps.HandleDownloadCollection)
return deps
}
// success sends a success JSON response.
func success(c fiber.Ctx, data any) error {
return c.JSON(fiber.Map{"status": "success", "data": data})
}
// failed sends a failed JSON response.
func failed(c fiber.Ctx, message string, code int) error {
return c.Status(code).JSON(fiber.Map{
"status": "failed",
"error": fiber.Map{"code": code, "message": message},
})
}