package backend import ( "context" "errors" "log" "net" "net/http" "path/filepath" "time" "autolibrary.top/webserver/backend/database/bulletins_database" "autolibrary.top/webserver/backend/database/themes_database" "autolibrary.top/webserver/backend/database/versions_database" "autolibrary.top/webserver/backend/handlers/bulletins_handler" "autolibrary.top/webserver/backend/handlers/errors_handler" "autolibrary.top/webserver/backend/handlers/health_handler" "autolibrary.top/webserver/backend/handlers/themes_handler" "autolibrary.top/webserver/backend/handlers/versions_handler" "autolibrary.top/webserver/backend/middlewares/auth_middleware" "autolibrary.top/webserver/backend/middlewares/casdoor_middleware" "autolibrary.top/webserver/backend/services/theme_files" "autolibrary.top/webserver/backend/services/users_service" "autolibrary.top/webserver/config" "autolibrary.top/webserver/utils" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" "github.com/gin-gonic/gin" ) // StartBackend : initializes the backend server. func StartBackend() error { return StartBackendContext(context.Background()) } func StartBackendContext(ctx context.Context) error { defer EndBackend() cfg := config.GetConfig() gin.SetMode(gin.ReleaseMode) err := versions_database.InitDB(cfg.Backend.Database.Versions.Path) if err != nil { log.Printf("[E] versions database initialization failed: %v", err) return err } err = bulletins_database.InitDB(cfg.Backend.Database.Bulletins.Path) if err != nil { log.Printf("[E] bulletins database initialization failed: %v", err) return err } err = themes_database.InitDB(cfg.Backend.Database.Themes.Path) if err != nil { log.Printf("[E] themes database initialization failed: %v", err) return err } files, err := theme_files.NewStore(cfg.Backend.Database.ThemesFiles.Path) if err != nil { return err } users := users_service.New(cfg.Backend.Casdoor.Endpoint != "" && cfg.Backend.Casdoor.ClientID != "") themes := themes_handler.New(themes_database.DB, cfg, files, users) // Pre-initialize Casdoor SDK and user display-name cache if cfg.Backend.Casdoor.Endpoint != "" && cfg.Backend.Casdoor.ClientID != "" { casdoorsdk.InitConfig( cfg.Backend.Casdoor.Endpoint, cfg.Backend.Casdoor.ClientID, cfg.Backend.Casdoor.ClientSecret, cfg.Backend.Casdoor.Certificate, cfg.Backend.Casdoor.Organization, cfg.Backend.Casdoor.Application, ) casdoorsdk.SetHttpClient(&http.Client{Timeout: 15 * time.Second}) } r := gin.New() r.MaxMultipartMemory = 8 << 20 r.Use(func(c *gin.Context) { c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 16<<20); c.Next() }) r.Use(gin.Recovery()) r.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key, X-Requested-With") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) templateDir := filepath.Join(utils.GetRootPath(), "backend", "templates") templateFiles := []string{ filepath.Join(templateDir, "404.html"), } r.LoadHTMLFiles(templateFiles...) public := r.Group("/") { public.GET("/health", health_handler.GetHealth) public.GET("/download/latest", versions_handler.GetLatestDownload) public.GET("/download/versions", versions_handler.GetDownloadVersions) public.GET("/download/versions/:id", versions_handler.GetDownloadVersionByID) public.GET("/bulletins", bulletins_handler.GetBulletins) public.GET("/bulletins/:id", bulletins_handler.GetBulletinByID) } // Marketplace public routes mp := r.Group("/marketplace") { mp.GET("/auth/config", themes.GetAuthConfig) mp.GET("/auth/captcha", themes.GetCaptcha) mp.POST("/auth/login", themes.LoginProxy) mp.POST("/auth/signup", themes.SignupProxy) mp.GET("/themes/featured", themes.GetFeaturedThemes) mp.GET("/themes/preview/:filename", themes.ServePreview) mp.GET("/themes", themes.GetThemes) mp.GET("/themes/:id", themes.GetThemeByID) mp.GET("/themes/:id/download", themes.DownloadTheme) } // Marketplace admin routes (API key) mpAdmin := r.Group("/marketplace") mpAdmin.Use(auth_middleware.APIKeyAuth()) { mpAdmin.PUT("/admin/themes/:id/review", themes.ReviewTheme) mpAdmin.GET("/admin/themes/pending", themes.GetPendingThemes) } // Marketplace user routes (Casdoor JWT) mpUser := r.Group("/marketplace") mpUser.Use(casdoor_middleware.CasdoorAuth()) { mpUser.POST("/themes", themes.PostTheme) mpUser.PUT("/themes/:id", themes.PutThemeByID) mpUser.DELETE("/themes/:id", themes.DeleteThemeByID) mpUser.GET("/user/check-theme-name", themes.CheckThemeName) mpUser.POST("/themes/:id/preview", themes.UploadPreview) mpUser.POST("/themes/:id/file", themes.ReplaceThemeFile) mpUser.GET("/user/themes", themes.GetUserThemes) mpUser.GET("/user/downloads", themes.GetUserDownloads) mpUser.GET("/user/role", themes.GetUserRole) } // Marketplace admin routes (Casdoor JWT + admin check) mpAdminUser := r.Group("/marketplace") mpAdminUser.Use(casdoor_middleware.CasdoorAdminAuth()) { mpAdminUser.GET("/admin/pending", themes.GetPendingThemes) mpAdminUser.PUT("/admin/review/:id", themes.ReviewTheme) mpAdminUser.GET("/admin/download/:id", themes.AdminDownloadTheme) } protected := r.Group("/") protected.Use(auth_middleware.APIKeyAuth()) { protected.POST("/download/versions", versions_handler.PostDownloadVersion) protected.PUT("/download/versions/:id", versions_handler.PutDownloadVersionByID) protected.DELETE("/download/versions/:id", versions_handler.DeleteDownloadVersionByID) protected.POST("/bulletins", bulletins_handler.PostBulletin) protected.PUT("/bulletins/:id", bulletins_handler.PutBulletinByID) protected.DELETE("/bulletins/:id", bulletins_handler.DeleteBulletinByID) } r.NoRoute(errors_handler.GetNotFoundError) backend_address := net.JoinHostPort(cfg.Backend.Host, cfg.Backend.Port) log.Printf("[I] backend server is running on: http://%s", backend_address) return utils.ServeHTTP(ctx, backend_address, r) } // EndBackend : closes the database connection. func EndBackend() error { return errors.Join(versions_database.CloseDB(), bulletins_database.CloseDB(), themes_database.CloseDB()) }