package transport import ( "time" "github.com/geo-platform/tenant-api/internal/bootstrap" "github.com/geo-platform/tenant-api/internal/shared/auth" "github.com/geo-platform/tenant-api/internal/shared/middleware" "github.com/geo-platform/tenant-api/internal/shared/swagger" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) func RegisterRoutes(app *bootstrap.App) { registerInternalMetricsRoutes(app) authAPI := app.Engine.Group("/api/auth") authHandler := NewAuthHandler(app) authAPI.GET("/password-key", authHandler.PasswordPublicKey) authAPI.POST("/login", authHandler.Login) authAPI.POST("/refresh", authHandler.Refresh) publicAssets := NewAssetHandler(app) app.Engine.GET("/api/public/assets/:token", publicAssets.Serve) mediaHandler := NewMediaHandler(app) protected := app.Engine.Group("/api") protected.Use(auth.Middleware(app.JWT, app.Sessions)) protected.Use(middleware.TenantScope()) protected.Use(middleware.WorkspaceScope(app.DB)) protected.Use(middleware.BrandScope(app.DB)) protected.GET("/auth/me", authHandler.Me) protected.POST("/auth/password", authHandler.ChangePassword) protected.POST("/auth/logout", authHandler.Logout) desktopClientHandler := NewDesktopClientHandler(app) desktopReleaseHandler := NewDesktopReleaseHandler(app) desktopBugReportHandler := NewDesktopBugReportHandler(app) desktopAccountHandler := NewDesktopAccountHandler(app) desktopTaskHandler := NewDesktopTaskHandler(app) desktopDispatchHandler := NewDesktopDispatchHandler(app) desktopMonitoringHandler := NewDesktopMonitoringHandler(app) desktopContentHandler := NewDesktopContentHandler(app) publishJobHandler := NewPublishJobHandler(app) protected.POST("/desktop/clients/register", desktopClientHandler.Register) app.Engine.GET("/api/desktop/releases/latest", desktopReleaseHandler.LatestPublic) app.Engine.GET("/api/desktop/releases/download-url", desktopReleaseHandler.DownloadURLPublic) app.Engine.GET("/api/desktop/releases/updater/:platform/:arch/:channel/:version/:feed", desktopReleaseHandler.UpdaterFeedPublic) app.Engine.GET("/api/desktop/releases/updater/:platform/:arch/:channel/:version", desktopReleaseHandler.UpdaterFeedPublic) publicBugReportRateLimit := middleware.RateLimitByClientIP(10, time.Minute, 42961, "desktop_bug_report_rate_limited", "客户端 Bug 上报过于频繁,请稍后再试") app.Engine.POST("/api/desktop/bug-reports", publicBugReportRateLimit, desktopBugReportHandler.UploadPublic) desktopAuth := app.Engine.Group("/api/desktop") desktopAuth.Use(DesktopClientMiddleware(repository.NewDesktopClientRepository(app.DB))) desktopAuth.POST("/clients/rotate", desktopClientHandler.Rotate) desktopAuth.POST("/clients/heartbeat", desktopClientHandler.Heartbeat) desktopAuth.POST("/clients/offline", desktopClientHandler.Offline) desktopAuth.POST("/clients/revoke", desktopClientHandler.Revoke) desktopAuth.POST("/clients/bug-reports", desktopBugReportHandler.Upload) desktopAuth.GET("/clients/releases/latest", desktopReleaseHandler.Latest) desktopAuth.GET("/clients/releases/download-url", desktopReleaseHandler.DownloadURL) desktopAuth.GET("/dispatch", desktopDispatchHandler.Dispatch) desktopAuth.GET("/accounts", desktopAccountHandler.List) desktopAuth.GET("/content/articles/:id", desktopContentHandler.Article) desktopAuth.GET("/content/assets/:token", publicAssets.ServeDesktop) desktopAuth.GET("/publish-tasks", desktopTaskHandler.ListPublish) desktopAuth.POST("/accounts/health-reports", desktopAccountHandler.ReportHealth) desktopAuth.POST("/accounts", desktopAccountHandler.Upsert) desktopAuth.PATCH("/accounts/:id", desktopAccountHandler.Patch) desktopAuth.DELETE("/accounts/:id", desktopAccountHandler.Delete) desktopAuth.POST("/tasks/lease", desktopTaskHandler.Lease) desktopAuth.POST("/tasks/:id/lease", desktopTaskHandler.Lease) desktopAuth.POST("/tasks/:id/extend", desktopTaskHandler.Extend) desktopAuth.POST("/tasks/:id/publish-submit-marker", desktopTaskHandler.MarkPublishSubmitStarted) desktopAuth.POST("/tasks/:id/cancel", desktopTaskHandler.Cancel) desktopAuth.POST("/tasks/:id/result", desktopTaskHandler.Result) desktopAuth.POST("/publish-tasks/:id/retry", desktopTaskHandler.RetryPublish) desktopAuth.POST("/monitoring/tasks/lease", desktopMonitoringHandler.Lease) desktopAuth.POST("/monitoring/tasks/resume", desktopMonitoringHandler.Resume) desktopAuth.POST("/monitoring/tasks/:id/result", desktopMonitoringHandler.Result) desktopAuth.POST("/monitoring/tasks/:id/skip", desktopMonitoringHandler.Skip) desktopAuth.POST("/monitoring/tasks/:id/cancel", desktopMonitoringHandler.Cancel) tenantProtected := protected.Group("/tenant") tenantProtected.Use(RequireActiveTenantSubscription( repository.NewTenantPlanRepository(app.DB), repository.NewAuthRepository(app.DB), )) tenantProtected.GET("/accounts", desktopAccountHandler.TenantList) tenantProtected.POST("/accounts/:id/request-delete", desktopAccountHandler.RequestDelete) tenantProtected.POST("/tasks/:id/reconcile", desktopTaskHandler.Reconcile) tenantProtected.POST("/tasks/:id/cancel", desktopTaskHandler.TenantCancel) tenantProtected.GET("/publish-tasks", desktopTaskHandler.TenantListPublish) tenantProtected.POST("/publish-tasks/:id/retry", middleware.RequireCurrentBrand(), desktopTaskHandler.TenantRetryPublish) tenantProtected.POST("/publish-jobs", middleware.RequireCurrentBrand(), publishJobHandler.Create) complianceHandler := NewComplianceHandler(app) compliance := tenantProtected.Group("/compliance") compliance.GET("/runtime-status", complianceHandler.RuntimeStatus) compliance.POST("/check", complianceHandler.CheckPlain) compliance.POST("/check-records/:id/ack", complianceHandler.CreateAck) media := tenantProtected.Group("/media") media.GET("/platforms", mediaHandler.ListPlatforms) enterpriseSiteHandler := NewEnterpriseSiteHandler(app) enterpriseSites := tenantProtected.Group("/enterprise-sites") enterpriseSites.GET("", enterpriseSiteHandler.List) enterpriseSites.POST("", enterpriseSiteHandler.Create) enterpriseSites.PATCH("/:id", enterpriseSiteHandler.Update) enterpriseSites.DELETE("/:id", enterpriseSiteHandler.Delete) enterpriseSites.POST("/:id/ping", enterpriseSiteHandler.Ping) enterpriseSites.GET("/:id/categories", enterpriseSiteHandler.Categories) enterpriseSites.POST("/:id/categories/sync", enterpriseSiteHandler.SyncCategories) enterpriseSites.POST("/:id/publish", middleware.RequireCurrentBrand(), enterpriseSiteHandler.PublishArticle) desktopClient := tenantProtected.Group("/desktop-client") desktopClient.GET("/releases/downloadable", desktopReleaseHandler.ListDownloadable) mediaSupplyHandler := NewMediaSupplyHandler(app) mediaSupply := tenantProtected.Group("/media-supply") mediaSupply.GET("/resources", mediaSupplyHandler.ListResources) mediaSupply.GET("/resources/by-ids", mediaSupplyHandler.ListResourcesByIDs) mediaSupply.GET("/resources/search-options", mediaSupplyHandler.SearchOptions) mediaSupply.POST("/sync-jobs", mediaSupplyHandler.QueueSync) mediaSupply.PUT("/resources/:id/price", mediaSupplyHandler.SetResourcePrice) mediaSupply.POST("/orders", middleware.RequireCurrentBrand(), mediaSupplyHandler.CreateOrder) mediaSupply.GET("/orders", mediaSupplyHandler.ListOrders) mediaSupply.GET("/orders/:id", mediaSupplyHandler.GetOrder) mediaSupply.GET("/wallet", mediaSupplyHandler.WalletStatus) mediaSupply.GET("/wallet/ledgers", mediaSupplyHandler.ListWalletLedgers) mediaSupply.POST("/wallet/adjustments", mediaSupplyHandler.AdjustWallet) mediaSupply.GET("/supplier-session", mediaSupplyHandler.SessionStatus) mediaSupply.PUT("/supplier-session", mediaSupplyHandler.ImportSession) mediaSupply.POST("/supplier-session/login", mediaSupplyHandler.LoginConfiguredAccount) workspace := tenantProtected.Group("/workspace") wsHandler := NewWorkspaceHandler(app) workspace.GET("/overview", middleware.RequireCurrentBrand(), wsHandler.Overview) workspace.GET("/recent-articles", middleware.RequireCurrentBrand(), wsHandler.RecentArticles) workspace.GET("/quota-summary", wsHandler.QuotaSummary) workspace.GET("/ai-point-usage", wsHandler.AIPointUsageLogs) workspace.GET("/template-cards", wsHandler.TemplateCards) workspace.GET("/kol-cards", wsHandler.KolCards) templates := tenantProtected.Group("/templates") tplHandler := NewTemplateHandler(app) templates.GET("", tplHandler.List) templates.GET("/:id", tplHandler.Detail) templates.POST("/:id/drafts", middleware.RequireCurrentBrand(), tplHandler.SaveDraft) templates.POST("/:id/analyze-tasks", tplHandler.CreateAnalyzeTask) templates.GET("/:id/analyze_task_result", tplHandler.GetAnalyzeTaskResult) templates.POST("/:id/title-tasks", tplHandler.CreateTitleTask) templates.GET("/:id/gen_title_task_result", tplHandler.GetTitleTaskResult) templates.POST("/:id/outline-tasks", tplHandler.CreateOutlineTask) templates.GET("/:id/gen_outline_task_result", tplHandler.GetOutlineTaskResult) templates.POST("/:id/generate", middleware.RequireCurrentBrand(), tplHandler.Generate) kolManage := tenantProtected.Group("/kol/manage") kolManageHandler := NewKolManageHandler(app, publicAssets) kolManage.GET("/profile", kolManageHandler.GetProfile) kolManage.PUT("/profile", kolManageHandler.UpdateProfile) kolManage.POST("/profile/avatar", kolManageHandler.UploadAvatar) kolManage.GET("/packages", kolManageHandler.ListPackages) kolManage.POST("/packages", kolManageHandler.CreatePackage) kolManage.PUT("/packages/:id", kolManageHandler.UpdatePackage) kolManage.DELETE("/packages/:id", kolManageHandler.DeletePackage) kolManage.PUT("/packages/:id/publish", kolManageHandler.PublishPackage) kolManage.PUT("/packages/:id/archive", kolManageHandler.ArchivePackage) kolManage.POST("/packages/:id/self-subscription", kolManageHandler.SelfSubscribePackage) kolManage.DELETE("/packages/:id/self-subscription", kolManageHandler.CancelSelfSubscription) kolManage.GET("/packages/:id/prompts", kolManageHandler.ListPrompts) kolManage.POST("/packages/:id/prompts", kolManageHandler.CreatePrompt) kolManage.GET("/prompts/:id", kolManageHandler.GetPromptLatest) kolManage.PUT("/prompts/:id", kolManageHandler.UpdatePromptMetadata) kolManage.DELETE("/prompts/:id", kolManageHandler.DeletePrompt) kolManage.POST("/prompts/:id/save", kolManageHandler.SavePrompt) kolManage.POST("/prompts/:id/publish", kolManageHandler.PublishPrompt) kolManage.PUT("/prompts/:id/activate", kolManageHandler.ActivatePrompt) kolManage.PUT("/prompts/:id/archive", kolManageHandler.ArchivePrompt) kolManage.POST("/assist", kolManageHandler.AssistSubmit) kolManage.POST("/assist/stream", kolManageHandler.AssistStream) kolManage.GET("/assist/:id", kolManageHandler.AssistGet) kolDash := tenantProtected.Group("/kol/dashboard") kolDashboardHandler := NewKolDashboardHandler(app) kolDash.GET("/overview", kolDashboardHandler.Overview) kolDash.GET("/packages", kolDashboardHandler.Packages) kolDash.GET("/trend", kolDashboardHandler.Trend) kolMarketplace := tenantProtected.Group("/kol/marketplace") kolMarketplaceHandler := NewKolMarketplaceHandler(app) kolMarketplace.GET("/packages", kolMarketplaceHandler.ListPackages) kolMarketplace.GET("/packages/:id", kolMarketplaceHandler.GetPackage) kolMarketplace.POST("/packages/:id/subscribe", kolMarketplaceHandler.Subscribe) kolSubscriptions := tenantProtected.Group("/kol") kolSubscriptions.GET("/subscriptions", kolMarketplaceHandler.ListSubscriptions) kolSubscriptions.GET("/subscription-prompts", kolMarketplaceHandler.ListSubscriptionPrompts) kolSubscriptions.GET("/subscription-prompts/:id/schema", kolMarketplaceHandler.SubscriptionPromptSchema) kolSubscriptions.POST("/subscription-prompts/:id/generate", middleware.RequireCurrentBrand(), kolMarketplaceHandler.Generate) articles := tenantProtected.Group("/articles") artHandler := NewArticleHandler(app) articles.Use(middleware.RequireCurrentBrand()) articles.GET("", artHandler.List) articles.POST("", artHandler.Create) articles.POST("/generate-from-rule", artHandler.GenerateFromRule) articles.POST("/imitations/generate", artHandler.GenerateImitation) articleRecords := articles.Group("/:id") articleRecords.Use(middleware.ArticleBrandScope(app.DB)) articleRecords.POST("/regenerate", artHandler.Regenerate) articleRecords.POST("/images", artHandler.UploadImage) articleRecords.POST("/selection-optimize/stream", artHandler.OptimizeSelectionStream) articleRecords.GET("", artHandler.Detail) articleRecords.PUT("", artHandler.Update) articleRecords.GET("/stream", artHandler.Stream) articleRecords.GET("/versions", artHandler.Versions) articleRecords.POST("/compliance/check", complianceHandler.CheckArticle) articleRecords.GET("/compliance/latest", complianceHandler.LatestCheck) articleRecords.POST("/compliance/manual-reviews", complianceHandler.SubmitManualReview) articleRecords.GET("/compliance/manual-reviews/latest", complianceHandler.LatestManualReview) articleRecords.POST("/compliance/manual-reviews/:review_id/cancel", complianceHandler.CancelManualReview) articleRecords.GET("/publish-records", mediaHandler.ListPublishRecords) articleRecords.DELETE("", artHandler.Delete) brands := tenantProtected.Group("/brands") brandHandler := NewBrandHandler(app) questionExpansionHandler := NewQuestionExpansionHandler(app) brands.GET("", brandHandler.List) brands.GET("/library-summary", brandHandler.Summary) brands.POST("", brandHandler.Create) brands.GET("/:id", brandHandler.Detail) brands.PUT("/:id", brandHandler.Update) brands.DELETE("/:id", brandHandler.Delete) brands.GET("/:id/keywords", brandHandler.ListKeywords) brands.POST("/:id/keywords", brandHandler.CreateKeyword) brands.PUT("/:id/keywords/:kid", brandHandler.UpdateKeyword) brands.DELETE("/:id/keywords/:kid", brandHandler.DeleteKeyword) brands.GET("/:id/questions", brandHandler.ListQuestions) brands.POST("/:id/questions", brandHandler.CreateQuestion) brands.POST("/:id/questions/combination-preview", questionExpansionHandler.CombinationPreview) brands.POST("/:id/questions/combination-fill", questionExpansionHandler.CombinationFill) brands.POST("/:id/questions/ai-distill", questionExpansionHandler.AIDistill) brands.POST("/:id/questions/classify-metadata", questionExpansionHandler.ClassifyMetadata) brands.POST("/:id/questions/materialize", questionExpansionHandler.Materialize) brands.PUT("/:id/questions/:qid", brandHandler.UpdateQuestion) brands.DELETE("/:id/questions/:qid", brandHandler.DeleteQuestion) brands.GET("/:id/competitors", brandHandler.ListCompetitors) brands.POST("/:id/competitors", brandHandler.CreateCompetitor) brands.PUT("/:id/competitors/:cid", brandHandler.UpdateCompetitor) brands.DELETE("/:id/competitors/:cid", brandHandler.DeleteCompetitor) monitoring := tenantProtected.Group("/monitoring") monitoringHandler := NewMonitoringHandler(app) monitoring.GET("/dashboard/composite", monitoringHandler.DashboardComposite) monitoring.GET("/citation-summary", monitoringHandler.CitationSummary) monitoring.GET("/brands/:brand_id/questions/:question_id/detail", monitoringHandler.QuestionDetail) monitoring.POST("/brands/:brand_id/collect-now", monitoringHandler.CollectNow) knowledge := tenantProtected.Group("/knowledge") knowledgeHandler := NewKnowledgeHandler(app) knowledge.GET("/groups", knowledgeHandler.ListGroups) knowledge.POST("/groups", knowledgeHandler.CreateGroup) knowledge.PUT("/groups/:gid", knowledgeHandler.UpdateGroup) knowledge.DELETE("/groups/:gid", knowledgeHandler.DeleteGroup) knowledge.GET("/items", knowledgeHandler.ListItems) knowledge.GET("/items/:id", knowledgeHandler.GetItemDetail) knowledge.POST("/items/text", knowledgeHandler.CreateTextItem) knowledge.POST("/items/url", knowledgeHandler.CreateURLItem) knowledge.POST("/items/file", knowledgeHandler.CreateFileItem) knowledge.POST("/items/retry/:id", knowledgeHandler.RetryItem) knowledge.DELETE("/items/:id", knowledgeHandler.DeleteItem) // Prompt Rules promptRules := tenantProtected.Group("/prompt-rules") promptRules.Use(middleware.RequireCurrentBrand()) prHandler := NewPromptRuleHandler(app) promptRules.GET("", prHandler.List) promptRules.GET("/simple", prHandler.ListSimple) promptRules.POST("", prHandler.Create) promptRules.GET("/:id", prHandler.Detail) promptRules.PUT("/:id", prHandler.Update) promptRules.DELETE("/:id", prHandler.Delete) promptRules.PUT("/:id/status", prHandler.ToggleStatus) // Prompt Rule Groups promptRules.GET("/groups", prHandler.ListGroups) promptRules.POST("/groups", prHandler.CreateGroup) promptRules.PUT("/groups/:gid", prHandler.UpdateGroup) promptRules.DELETE("/groups/:gid", prHandler.DeleteGroup) // Schedule Tasks schedules := tenantProtected.Group("/schedules") schHandler := NewScheduleTaskHandler(app) schedules.Use(middleware.RequireCurrentBrand()) schedules.GET("", schHandler.List) schedules.POST("", schHandler.Create) schedules.GET("/:id", schHandler.Detail) schedules.PUT("/:id", schHandler.Update) schedules.DELETE("/:id", schHandler.Delete) schedules.PUT("/:id/status", schHandler.ToggleStatus) instantTasks := tenantProtected.Group("/instant-tasks") instHandler := NewInstantTaskHandler(app) instantTasks.GET("", middleware.RequireCurrentBrand(), instHandler.List) images := tenantProtected.Group("/images") imgHandler := NewImageHandler(app) images.GET("", imgHandler.ListImages) images.POST("", imgHandler.UploadImage) images.PUT("/:id", imgHandler.UpdateImage) images.GET("/:id/references", imgHandler.GetImageReferences) images.DELETE("/:id", imgHandler.DeleteImage) images.GET("/storage-usage", imgHandler.StorageUsage) imageFolders := tenantProtected.Group("/images/folders") imageFolders.GET("", imgHandler.ListFolders) imageFolders.POST("", imgHandler.CreateFolder) imageFolders.PUT("/:id", imgHandler.UpdateFolder) imageFolders.GET("/:id/delete-preview", imgHandler.FolderDeletePreview) imageFolders.DELETE("/:id", imgHandler.DeleteFolder) if swagger.EnabledForMode(app.Config().Server.Mode) { swagger.Register(app.Engine, swagger.Config{ Title: "省心推 Admin API", Version: "dev", }) } }