对齐 vivid 6039423/5802749/a1f808f/cfb6d49/aba8474/b01636f: - 新增 POST /channels/sync-models:已保存渠道手动全量同步,上游不可达/ 空清单直接报错(保存路径仍降级为告警);渠道行按钮不再弹勾选框, 表单内对已保存渠道同样直同步,新渠道保持预览+勾选即绑定 - 渠道并发 0 = 不限制(原 0=默认5),上限放开到 int32,槽位 Lua 本就 max=0 放行 - 别名与上游模型同名提交即报错「留空即同名」,前后端双校验 - 渠道列表去掉上游地址与模型两列(同 vivid),状态徽章悬浮显示失败原因 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
214 lines
9.7 KiB
Go
214 lines
9.7 KiB
Go
package router
|
|
|
|
import (
|
|
"backend/internal/config"
|
|
"backend/internal/http/handler"
|
|
"backend/internal/http/middleware"
|
|
"backend/internal/service"
|
|
|
|
"github.com/gin-contrib/cors"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type Handlers struct {
|
|
Health *handler.HealthHandler
|
|
Images *handler.ImageHandler
|
|
V1 *handler.V1Handler
|
|
Site *handler.SiteHandler
|
|
Showcase *handler.ShowcaseHandler
|
|
Auth *handler.AuthHandler
|
|
SiteSettings *handler.SiteSettingsHandler
|
|
AppSettings *handler.AppSettingsHandler
|
|
AdminRead *handler.AdminReadHandler
|
|
AdminWrite *handler.AdminWriteHandler
|
|
CDK *handler.CDKHandler
|
|
UserTools *handler.UserToolsHandler
|
|
UserGen *handler.UserGenerationHandler
|
|
ChannelAdmin *handler.ChannelAdminHandler
|
|
ConcGroups *handler.ConcurrencyGroupHandler
|
|
Announcement *handler.AnnouncementHandler
|
|
Payment *handler.PaymentHandler
|
|
BannedWords *handler.BannedWordsHandler
|
|
Canvas *handler.CanvasHandler
|
|
}
|
|
|
|
func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.Engine {
|
|
if cfg.AppEnv != "development" {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
|
|
engine := gin.New()
|
|
// Only the in-stack reverse proxies (traefik → frontend nginx, all on
|
|
// private docker networks) may supply forwarding headers; ClientIP then
|
|
// resolves the real client instead of a spoofable X-Forwarded-For.
|
|
_ = engine.SetTrustedProxies([]string{
|
|
"10.0.0.0/8",
|
|
"172.16.0.0/12",
|
|
"192.168.0.0/16",
|
|
"127.0.0.0/8",
|
|
"::1/128",
|
|
})
|
|
engine.Use(gin.Recovery())
|
|
engine.Use(middleware.RequestID())
|
|
engine.Use(cors.New(cors.Config{
|
|
AllowOrigins: cfg.CORSOrigins,
|
|
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
|
|
AllowHeaders: []string{"Authorization", "Content-Type", "X-Request-Id"},
|
|
AllowCredentials: true,
|
|
}))
|
|
|
|
engine.GET("/health", handlers.Health.Handle)
|
|
engine.GET("/images/:user/:name", handlers.Images.Serve)
|
|
// vivid-protocol public API — same four endpoints a vivid instance exposes,
|
|
// which is what lets this service act as a channel upstream itself.
|
|
engine.POST("/v1/generate", handlers.V1.Generate)
|
|
engine.POST("/v1/uploads", handlers.V1.Uploads)
|
|
engine.GET("/v1/models", handlers.V1.Models)
|
|
engine.GET("/v1/balance", handlers.V1.UserBalance)
|
|
|
|
publicAdmin := engine.Group("/admin/api")
|
|
{
|
|
publicAdmin.GET("/site", handlers.Site.Public)
|
|
publicAdmin.GET("/showcase", handlers.Showcase.List)
|
|
publicAdmin.GET("/managed-models", handlers.AdminRead.Models)
|
|
// Aggregates only (counts/sizes/latency) — the recent-files list with
|
|
// owner names is admin-only, see /stats/recent below.
|
|
publicAdmin.GET("/stats", handlers.AdminRead.Stats)
|
|
publicAdmin.GET("/video-presets", handlers.UserGen.VideoPresets)
|
|
publicAdmin.GET("/models", handlers.UserGen.Models)
|
|
publicAdmin.GET("/deai-pricing", handlers.AppSettings.DeAIGet)
|
|
// 易支付 async notify — called server-to-server by the pay platform, no auth.
|
|
publicAdmin.GET("/pay/notify", handlers.Payment.Notify)
|
|
publicAdmin.POST("/pay/notify", handlers.Payment.Notify)
|
|
}
|
|
|
|
authGroup := engine.Group("/admin/api/auth")
|
|
{
|
|
authGroup.GET("/config", handlers.Auth.Config)
|
|
authGroup.POST("/send-code", handlers.Auth.SendCode)
|
|
authGroup.POST("/register", handlers.Auth.Register)
|
|
authGroup.POST("/login", handlers.Auth.Login)
|
|
authGroup.POST("/logout", handlers.Auth.Logout)
|
|
authGroup.POST("/reset-password", handlers.Auth.ResetPassword)
|
|
}
|
|
|
|
userAuthed := engine.Group("/admin/api")
|
|
userAuthed.Use(middleware.RequireSession(auth, cfg))
|
|
{
|
|
userAuthed.GET("/logs", handlers.UserGen.Logs)
|
|
userAuthed.POST("/generate", handlers.UserGen.Generate)
|
|
userAuthed.POST("/test", handlers.UserGen.Test)
|
|
userAuthed.GET("/jobs/mine", handlers.UserGen.MyJobs)
|
|
userAuthed.GET("/my-images", handlers.UserGen.MyImages)
|
|
userAuthed.POST("/my-images", handlers.UserGen.UploadMyImage)
|
|
userAuthed.DELETE("/my-files", handlers.UserGen.DeleteMyFile)
|
|
userAuthed.GET("/announcement", handlers.Announcement.Get)
|
|
userAuthed.POST("/announcement/seen", handlers.Announcement.MarkSeen)
|
|
userAuthed.GET("/pay/config", handlers.Payment.Config)
|
|
userAuthed.POST("/pay/recharge", handlers.Payment.Recharge)
|
|
userAuthed.GET("/pay/orders", handlers.Payment.MyOrders)
|
|
userAuthed.GET("/pay/orders/:id", handlers.Payment.OrderStatus)
|
|
userAuthed.POST("/pay/orders/:id/continue", handlers.Payment.ContinueOrder)
|
|
// 无限画布 documents — session-authed, owner-scoped.
|
|
userAuthed.GET("/canvas", handlers.Canvas.List)
|
|
userAuthed.POST("/canvas", handlers.Canvas.Create)
|
|
userAuthed.GET("/canvas/:id", handlers.Canvas.Get)
|
|
userAuthed.PUT("/canvas/:id", handlers.Canvas.Save)
|
|
userAuthed.DELETE("/canvas/:id", handlers.Canvas.Delete)
|
|
}
|
|
|
|
authed := engine.Group("/admin/api")
|
|
authed.Use(middleware.RequireAdminSession(auth, cfg))
|
|
{
|
|
authed.GET("/dashboard", handlers.AdminRead.Dashboard)
|
|
// Recent generated files include owner-directory names — admin only.
|
|
authed.GET("/stats/recent", handlers.AdminRead.StatsRecent)
|
|
authed.GET("/users", handlers.AdminRead.Users)
|
|
authed.GET("/invites", handlers.AdminRead.Invites)
|
|
authed.POST("/users", handlers.AdminWrite.CreateUser)
|
|
authed.POST("/users/delete-bulk", handlers.AdminWrite.DeleteUsersBulk)
|
|
authed.PATCH("/users/:user_id", handlers.AdminWrite.UpdateUser)
|
|
authed.DELETE("/users/:user_id", handlers.AdminWrite.DeleteUser)
|
|
authed.POST("/users/:user_id/credits", handlers.AdminWrite.AdjustUserCredits)
|
|
authed.POST("/users/:user_id/api-keys", handlers.AdminWrite.CreateUserAPIKey)
|
|
authed.DELETE("/users/:user_id/api-keys/:key_id", handlers.AdminWrite.DeleteUserAPIKey)
|
|
authed.GET("/concurrency-groups", handlers.ConcGroups.List)
|
|
authed.POST("/concurrency-groups", handlers.ConcGroups.Create)
|
|
authed.PATCH("/concurrency-groups/:id", handlers.ConcGroups.Update)
|
|
authed.POST("/concurrency-groups/:id/default", handlers.ConcGroups.SetDefault)
|
|
authed.DELETE("/concurrency-groups/:id", handlers.ConcGroups.Delete)
|
|
authed.GET("/pay/admin/orders", handlers.Payment.AdminOrders)
|
|
authed.GET("/cdks", handlers.CDK.List)
|
|
authed.POST("/cdks", handlers.CDK.Create)
|
|
authed.POST("/cdks/delete-bulk", handlers.CDK.DeleteBulk)
|
|
authed.DELETE("/cdks/:code", handlers.CDK.Delete)
|
|
authed.GET("/channels", handlers.ChannelAdmin.List)
|
|
authed.POST("/channels", handlers.ChannelAdmin.Save)
|
|
authed.POST("/channels/delete", handlers.ChannelAdmin.Delete)
|
|
authed.POST("/channels/fetch-models", handlers.ChannelAdmin.FetchModels)
|
|
authed.POST("/channels/sync-models", handlers.ChannelAdmin.SyncModels)
|
|
authed.POST("/channels/import-models", handlers.ChannelAdmin.ImportModels)
|
|
authed.POST("/channels/unbind-model", handlers.ChannelAdmin.UnbindModel)
|
|
authed.GET("/providers", handlers.AdminRead.Providers)
|
|
authed.GET("/images", handlers.AdminRead.Images)
|
|
authed.DELETE("/images", handlers.AdminRead.DeleteImage)
|
|
authed.GET("/banned-words", handlers.BannedWords.List)
|
|
authed.POST("/banned-words", handlers.BannedWords.Create)
|
|
authed.POST("/banned-words/import", handlers.BannedWords.Import)
|
|
authed.DELETE("/banned-words/:id", handlers.BannedWords.Delete)
|
|
authed.GET("/banned-word-hits", handlers.BannedWords.Hits)
|
|
authed.POST("/managed-models", handlers.AdminWrite.CreateModel)
|
|
authed.PATCH("/managed-models/:model_id", handlers.AdminWrite.UpdateModel)
|
|
authed.DELETE("/managed-models/:model_id", handlers.AdminWrite.DeleteModel)
|
|
authed.DELETE("/logs", handlers.AdminWrite.ClearLogs)
|
|
authed.DELETE("/logs/pending", handlers.AdminWrite.ClearPendingLogs)
|
|
authed.GET("/showcase/admin", handlers.Showcase.AdminList)
|
|
authed.POST("/showcase", handlers.AdminWrite.CreateShowcase)
|
|
authed.PATCH("/showcase/:entry_id", handlers.AdminWrite.UpdateShowcase)
|
|
authed.DELETE("/showcase/:entry_id", handlers.AdminWrite.DeleteShowcase)
|
|
|
|
settings := authed.Group("/settings")
|
|
{
|
|
settings.GET("/site", handlers.SiteSettings.Get)
|
|
settings.PUT("/site", handlers.SiteSettings.Put)
|
|
settings.POST("/logo", handlers.AppSettings.LogoUpload)
|
|
settings.DELETE("/logo", handlers.AppSettings.LogoDelete)
|
|
settings.POST("/asset", handlers.AppSettings.AssetUpload)
|
|
settings.GET("/registration", handlers.AppSettings.RegistrationGet)
|
|
settings.PUT("/registration", handlers.AppSettings.RegistrationPut)
|
|
settings.GET("/smtp", handlers.AppSettings.SMTPGet)
|
|
settings.PUT("/smtp", handlers.AppSettings.SMTPPut)
|
|
settings.POST("/smtp/test", handlers.AppSettings.SMTPTest)
|
|
settings.GET("/proxy", handlers.AppSettings.ProxyGet)
|
|
settings.PUT("/proxy", handlers.AppSettings.ProxyPut)
|
|
settings.POST("/proxy/test", handlers.AppSettings.ProxyTest)
|
|
settings.GET("/credits", handlers.AppSettings.CreditsGet)
|
|
settings.PUT("/credits", handlers.AppSettings.CreditsPut)
|
|
settings.GET("/logs", handlers.AppSettings.LogsGet)
|
|
settings.PUT("/logs", handlers.AppSettings.LogsPut)
|
|
settings.GET("/deai", handlers.AppSettings.DeAIGet)
|
|
settings.PUT("/deai", handlers.AppSettings.DeAIPut)
|
|
settings.GET("/media", handlers.AppSettings.MediaGet)
|
|
settings.PUT("/media", handlers.AppSettings.MediaPut)
|
|
settings.GET("/announcement", handlers.Announcement.AdminGet)
|
|
settings.PUT("/announcement", handlers.Announcement.AdminPut)
|
|
settings.GET("/pay", handlers.Payment.SettingsGet)
|
|
settings.PUT("/pay", handlers.Payment.SettingsSave)
|
|
}
|
|
}
|
|
|
|
authGroup.Use(middleware.RequireSession(auth, cfg))
|
|
{
|
|
authGroup.GET("/me", handlers.Auth.Me)
|
|
authGroup.GET("/invites", handlers.Auth.Invites)
|
|
authGroup.POST("/checkin", handlers.Auth.Checkin)
|
|
authGroup.POST("/change-password", handlers.Auth.ChangePassword)
|
|
authGroup.GET("/api-key", handlers.UserTools.APIKeyGet)
|
|
authGroup.POST("/api-key", handlers.UserTools.APIKeyMint)
|
|
authGroup.DELETE("/api-key", handlers.UserTools.APIKeyDelete)
|
|
authGroup.POST("/redeem-cdk", handlers.UserTools.RedeemCDK)
|
|
}
|
|
|
|
return engine
|
|
}
|