Build and Publish Docker Image / build-and-push (pull_request) Successful in 15m54s
- 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 配置、 缓存命中路径)
122 lines
4.1 KiB
Go
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},
|
|
})
|
|
}
|