62d0b22988
Desktop Client Build / Resolve Build Metadata (push) Successful in 52s
Frontend CI / Frontend (push) Successful in 3m49s
Backend CI / Backend (push) Successful in 16m10s
Desktop Client Build / Build Desktop Client (push) Successful in 23m22s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 31s
- Added new endpoints for initiating and completing direct uploads of desktop client packages. - Introduced request and response structures for direct upload operations. - Enhanced the upload process to support SHA256 and Content-MD5 validation. - Updated the frontend to reflect changes in upload button states and progress indicators. - Modified object storage clients to support presigned PUT URLs with content type and MD5 headers. - Added tests for direct upload functionality and ensured existing upload processes remain intact.
221 lines
12 KiB
Go
221 lines
12 KiB
Go
package transport
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/ops/app"
|
|
sharedconfig "github.com/geo-platform/tenant-api/internal/shared/config"
|
|
"github.com/geo-platform/tenant-api/internal/shared/middleware"
|
|
"github.com/geo-platform/tenant-api/internal/shared/response"
|
|
"github.com/geo-platform/tenant-api/internal/shared/swagger"
|
|
tenantrepo "github.com/geo-platform/tenant-api/internal/tenant/repository"
|
|
)
|
|
|
|
type Deps struct {
|
|
Engine *gin.Engine
|
|
Server sharedconfig.ServerConfig
|
|
Logger *zap.Logger
|
|
DB *pgxpool.Pool
|
|
MonitoringDB *pgxpool.Pool
|
|
Issuer *app.TokenIssuer
|
|
Auth *app.AuthService
|
|
Accounts *app.AccountService
|
|
AdminUsers *app.AdminUserService
|
|
Jobs *app.JobService
|
|
KolSubs *app.KolSubscriptionService
|
|
Audits *app.AuditService
|
|
SiteDomains *app.SiteDomainMappingService
|
|
Releases *app.DesktopClientReleaseService
|
|
ObjectStorage *app.ObjectStorageService
|
|
BugReports *app.DesktopBugReportService
|
|
Compliance *app.ComplianceService
|
|
Scheduler *app.SchedulerService
|
|
MediaSupply *app.MediaSupplyService
|
|
}
|
|
|
|
func (d Deps) ServerAllowedOrigins() []string {
|
|
return d.Server.AllowedOrigins
|
|
}
|
|
|
|
func (d Deps) ServerSecurityHeadersEnabled() bool {
|
|
return d.Server.SecurityHeaders.Enabled
|
|
}
|
|
|
|
func RegisterRoutes(d Deps) {
|
|
d.Engine.Use(
|
|
middleware.Recovery(d.Logger),
|
|
middleware.RequestID(),
|
|
middleware.Logger(d.Logger),
|
|
)
|
|
if d.ServerSecurityHeadersEnabled() {
|
|
d.Engine.Use(middleware.SecurityHeaders())
|
|
}
|
|
d.Engine.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
AllowedOrigins: d.ServerAllowedOrigins(),
|
|
AllowAll: len(d.ServerAllowedOrigins()) == 0,
|
|
}))
|
|
|
|
d.Engine.GET("/api/health/live", func(c *gin.Context) {
|
|
response.Success(c, gin.H{"status": "alive"})
|
|
})
|
|
d.Engine.GET("/api/health/ready", func(c *gin.Context) {
|
|
if err := d.DB.Ping(c.Request.Context()); err != nil {
|
|
response.Error(c, response.ErrServiceUnavailable(50301, "postgres_unavailable", "数据库不可达"))
|
|
return
|
|
}
|
|
if d.MonitoringDB != nil {
|
|
if err := d.MonitoringDB.Ping(c.Request.Context()); err != nil {
|
|
response.Error(c, response.ErrServiceUnavailable(50303, "monitoring_postgres_unavailable", "监控数据库不可达"))
|
|
return
|
|
}
|
|
}
|
|
response.Success(c, gin.H{"status": "ready"})
|
|
})
|
|
d.Engine.GET("/api/desktop/releases/latest", checkDesktopClientReleaseHandler(d.Releases))
|
|
d.Engine.GET("/api/desktop/releases/download-url", resolveLatestDesktopClientReleaseDownloadURLHandler(d.Releases))
|
|
d.Engine.GET("/api/desktop/releases/updater/:platform/:arch/:channel/:version/:feed", desktopClientReleaseUpdaterFeedHandler(d.Releases))
|
|
d.Engine.GET("/api/desktop/releases/updater/:platform/:arch/:channel/:version", desktopClientReleaseUpdaterFeedHandler(d.Releases))
|
|
publicBugReportRateLimit := middleware.RateLimitByClientIP(10, time.Minute, 42961, "desktop_bug_report_rate_limited", "客户端 Bug 上报过于频繁,请稍后再试")
|
|
d.Engine.POST("/api/desktop/bug-reports", publicBugReportRateLimit, uploadDesktopBugReportHandler(d.BugReports))
|
|
|
|
desktopAuthed := d.Engine.Group("/api/desktop")
|
|
desktopAuthed.Use(desktopBugReportClientMiddleware(tenantrepo.NewDesktopClientRepository(d.DB)))
|
|
desktopAuthed.POST("/clients/bug-reports", uploadDesktopBugReportForClientHandler(d.BugReports))
|
|
|
|
api := d.Engine.Group("/api/ops")
|
|
{
|
|
api.GET("/auth/password-key", passwordPublicKeyHandler(d.Auth))
|
|
api.POST("/auth/login", loginHandler(d.Auth))
|
|
}
|
|
|
|
authed := api.Group("")
|
|
authed.Use(authMiddleware(d.Issuer))
|
|
{
|
|
authed.GET("/auth/me", meHandler(d.Accounts))
|
|
authed.POST("/auth/password", changeOwnPasswordHandler(d.Accounts))
|
|
|
|
authed.GET("/accounts", listAccountsHandler(d.Accounts))
|
|
authed.POST("/accounts", createAccountHandler(d.Accounts))
|
|
authed.GET("/accounts/:id", getAccountHandler(d.Accounts))
|
|
authed.PATCH("/accounts/:id", updateAccountProfileHandler(d.Accounts))
|
|
authed.POST("/accounts/:id/status", setAccountStatusHandler(d.Accounts))
|
|
authed.POST("/accounts/:id/password", resetAccountPasswordHandler(d.Accounts))
|
|
|
|
authed.GET("/plans", listPlansHandler(d.AdminUsers))
|
|
authed.GET("/roles", listRolesHandler(d.AdminUsers))
|
|
|
|
authed.GET("/admin-users", listAdminUsersHandler(d.AdminUsers))
|
|
authed.POST("/admin-users", createAdminUserHandler(d.AdminUsers))
|
|
authed.GET("/admin-users/:id", getAdminUserHandler(d.AdminUsers))
|
|
authed.PATCH("/admin-users/:id", updateAdminUserHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/plan", changeAdminUserPlanHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/subscription-expiry", updateAdminUserSubscriptionExpiryHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/reset-plan-usage", resetAdminUserPlanUsageHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/role", changeAdminUserRoleHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/kol", setAdminUserKOLHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/status", setAdminUserStatusHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/password", resetAdminUserPasswordHandler(d.AdminUsers))
|
|
authed.POST("/admin-users/:id/reset-login-lock", resetAdminUserLoginLockHandler(d.AdminUsers))
|
|
|
|
authed.GET("/kol/packages", listKolSubscriptionPackagesHandler(d.KolSubs))
|
|
authed.GET("/kol/subscriptions", listKolSubscriptionsHandler(d.KolSubs))
|
|
authed.POST("/kol/subscriptions/manual-bind", manualBindKolSubscriptionHandler(d.KolSubs))
|
|
authed.POST("/kol/subscriptions/:id/approve", approveKolSubscriptionHandler(d.KolSubs))
|
|
authed.POST("/kol/subscriptions/:id/revoke", revokeKolSubscriptionHandler(d.KolSubs))
|
|
|
|
authed.GET("/audits", listAuditsHandler(d.Audits))
|
|
|
|
authed.GET("/jobs", listJobsHandler(d.Jobs))
|
|
authed.GET("/jobs/:source/:id", getJobHandler(d.Jobs))
|
|
authed.POST("/jobs/:source/:id/retry", retryJobHandler(d.Jobs))
|
|
authed.POST("/jobs/:source/:id/cancel", cancelJobHandler(d.Jobs))
|
|
|
|
authed.GET("/media-supply/resources", listMediaSupplyResourcesHandler(d.MediaSupply))
|
|
authed.PUT("/media-supply/resources/:id/price", setMediaSupplyResourcePriceHandler(d.MediaSupply))
|
|
authed.PUT("/media-supply/resources/:id/visibility", setMediaSupplyResourceVisibilityHandler(d.MediaSupply))
|
|
authed.POST("/media-supply/sync-jobs", queueMediaSupplySyncHandler(d.MediaSupply))
|
|
authed.GET("/media-supply/wallets", listMediaSupplyWalletsHandler(d.MediaSupply))
|
|
authed.POST("/media-supply/wallets/adjustments", adjustMediaSupplyWalletHandler(d.MediaSupply))
|
|
|
|
authed.GET("/scheduler/jobs", listSchedulerJobsHandler(d.Scheduler))
|
|
authed.GET("/scheduler/jobs/:key", getSchedulerJobHandler(d.Scheduler))
|
|
authed.PATCH("/scheduler/jobs/:key", updateSchedulerJobHandler(d.Scheduler))
|
|
authed.POST("/scheduler/jobs/:key/enable", enableSchedulerJobHandler(d.Scheduler, true))
|
|
authed.POST("/scheduler/jobs/:key/disable", enableSchedulerJobHandler(d.Scheduler, false))
|
|
authed.POST("/scheduler/jobs/:key/run", triggerSchedulerJobHandler(d.Scheduler, "manual"))
|
|
authed.POST("/scheduler/jobs/:key/dry-run", triggerSchedulerJobHandler(d.Scheduler, "dry_run"))
|
|
authed.GET("/scheduler/jobs/:key/runs", listSchedulerRunsHandler(d.Scheduler))
|
|
authed.GET("/scheduler/instances", listSchedulerInstancesHandler(d.Scheduler))
|
|
|
|
authed.GET("/site-domain-mappings", listSiteDomainMappingsHandler(d.SiteDomains))
|
|
authed.GET("/site-domain-mappings/export", exportSiteDomainMappingsHandler(d.SiteDomains))
|
|
authed.POST("/site-domain-mappings/import", importSiteDomainMappingsHandler(d.SiteDomains))
|
|
authed.POST("/site-domain-mappings", createSiteDomainMappingHandler(d.SiteDomains))
|
|
authed.PATCH("/site-domain-mappings/:id", updateSiteDomainMappingHandler(d.SiteDomains))
|
|
authed.POST("/site-domain-mappings/:id/active", setSiteDomainMappingActiveHandler(d.SiteDomains))
|
|
authed.DELETE("/site-domain-mappings/:id", deleteSiteDomainMappingHandler(d.SiteDomains))
|
|
|
|
authed.GET("/object-storage/objects", listObjectStorageObjectsHandler(d.ObjectStorage))
|
|
authed.GET("/object-storage/usage", getObjectStorageUsageHandler(d.ObjectStorage))
|
|
authed.GET("/object-storage/objects/detail", getObjectStorageObjectHandler(d.ObjectStorage))
|
|
authed.GET("/object-storage/objects/url", resolveObjectStorageObjectURLHandler(d.ObjectStorage))
|
|
authed.GET("/object-storage/objects/download-url", resolveObjectStorageObjectDownloadURLHandler(d.ObjectStorage))
|
|
authed.GET("/object-storage/objects/download", downloadObjectStorageObjectHandler(d.ObjectStorage))
|
|
authed.POST("/object-storage/objects/upload", uploadObjectStorageObjectHandler(d.ObjectStorage))
|
|
authed.POST("/object-storage/objects/move", moveObjectStorageObjectHandler(d.ObjectStorage))
|
|
authed.DELETE("/object-storage/objects", deleteObjectStorageObjectHandler(d.ObjectStorage))
|
|
authed.POST("/object-storage/folders", createObjectStorageFolderHandler(d.ObjectStorage))
|
|
authed.DELETE("/object-storage/folders", deleteObjectStorageFolderHandler(d.ObjectStorage))
|
|
|
|
authed.GET("/desktop-client/releases", listDesktopClientReleasesHandler(d.Releases))
|
|
authed.POST("/desktop-client/releases/upload", uploadDesktopClientReleaseHandler(d.Releases))
|
|
authed.POST("/desktop-client/releases/direct-upload", initiateDesktopClientReleaseDirectUploadHandler(d.Releases))
|
|
authed.POST("/desktop-client/releases/direct-upload/complete", completeDesktopClientReleaseDirectUploadHandler(d.Releases))
|
|
authed.POST("/desktop-client/release-uploads", initiateDesktopClientReleaseUploadHandler(d.Releases))
|
|
authed.POST("/desktop-client/release-uploads/:upload_id/chunks/:chunk_index", uploadDesktopClientReleaseChunkHandler(d.Releases))
|
|
authed.POST("/desktop-client/release-uploads/:upload_id/complete", completeDesktopClientReleaseUploadHandler(d.Releases))
|
|
authed.DELETE("/desktop-client/release-uploads/:upload_id", abortDesktopClientReleaseUploadHandler(d.Releases))
|
|
authed.POST("/desktop-client/releases", createDesktopClientReleaseHandler(d.Releases))
|
|
authed.PATCH("/desktop-client/releases/:id", updateDesktopClientReleaseHandler(d.Releases))
|
|
authed.GET("/desktop-client/releases/:id/download-url", resolveDesktopClientReleaseDownloadURLHandler(d.Releases))
|
|
authed.POST("/desktop-client/releases/:id/enabled", setDesktopClientReleaseEnabledHandler(d.Releases))
|
|
authed.DELETE("/desktop-client/releases/:id", deleteDesktopClientReleaseHandler(d.Releases))
|
|
authed.GET("/desktop-client/bug-reports", listDesktopBugReportsHandler(d.BugReports))
|
|
authed.GET("/desktop-client/bug-reports/:id", getDesktopBugReportHandler(d.BugReports))
|
|
authed.PATCH("/desktop-client/bug-reports/:id", updateDesktopBugReportHandler(d.BugReports))
|
|
authed.GET("/desktop-client/bug-reports/:id/dump-url", resolveDesktopBugReportDumpURLHandler(d.BugReports))
|
|
|
|
compliance := authed.Group("/compliance")
|
|
compliance.GET("/dictionaries", listComplianceDictionariesHandler(d.Compliance))
|
|
compliance.POST("/dictionaries", createComplianceDictionaryHandler(d.Compliance))
|
|
compliance.PUT("/dictionaries/:id", updateComplianceDictionaryHandler(d.Compliance))
|
|
compliance.DELETE("/dictionaries/:id", deleteComplianceDictionaryHandler(d.Compliance))
|
|
compliance.GET("/dictionaries/:id/terms", listComplianceTermsHandler(d.Compliance))
|
|
compliance.POST("/dictionaries/:id/terms", createComplianceTermHandler(d.Compliance))
|
|
compliance.POST("/dictionaries/:id/terms:batch-import", batchImportComplianceTermsHandler(d.Compliance))
|
|
compliance.DELETE("/dictionaries/:id/terms/:term_id", deleteComplianceTermHandler(d.Compliance))
|
|
compliance.POST("/dictionaries/:id/publish", publishComplianceDictionaryHandler(d.Compliance))
|
|
compliance.POST("/dictionaries/:id/rollback", rollbackComplianceDictionaryHandler(d.Compliance))
|
|
compliance.GET("/policy/global", getGlobalCompliancePolicyHandler(d.Compliance))
|
|
compliance.PUT("/policy/global", updateGlobalCompliancePolicyHandler(d.Compliance))
|
|
compliance.POST("/policy/global/master-switch", setGlobalComplianceMasterSwitchHandler(d.Compliance))
|
|
compliance.GET("/manual-reviews", listComplianceManualReviewsHandler(d.Compliance))
|
|
compliance.GET("/manual-reviews/:id", getComplianceManualReviewHandler(d.Compliance))
|
|
compliance.POST("/manual-reviews/:id/approve", approveComplianceManualReviewHandler(d.Compliance))
|
|
compliance.POST("/manual-reviews/:id/reject", rejectComplianceManualReviewHandler(d.Compliance))
|
|
compliance.GET("/stats", complianceStatsHandler(d.Compliance))
|
|
compliance.GET("/records", listComplianceRecordsHandler(d.Compliance))
|
|
}
|
|
|
|
if swagger.EnabledForMode(d.Server.Mode) {
|
|
swagger.Register(d.Engine, swagger.Config{
|
|
Title: "省心推 Ops API",
|
|
Version: "dev",
|
|
})
|
|
}
|
|
}
|