package server import ( "embed" "errors" "io/fs" "net/http" "path/filepath" "pcgamedb/crawler" "pcgamedb/db" "pcgamedb/log" "pcgamedb/server/handler" "pcgamedb/server/middleware" "strconv" "strings" "github.com/gin-contrib/cors" "github.com/gin-contrib/multitemplate" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" "pcgamedb/docs" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) //go:embed templates templates/layouts static var frontendFS embed.FS func initRoute(app *gin.Engine) { app.Use(cors.New(cors.Config{ AllowAllOrigins: true, })) initFrontend(app) initApi(app) } func initFrontend(app *gin.Engine) { app.Use(middleware.Logger()) // Load static files staticFs, err := fs.Sub(frontendFS, "static") if err != nil { log.Logger.Fatal("Error loading static files", zap.Error(err)) return } app.StaticFS("/static", http.FS(staticFs)) // Load templates // directly using templates app.LoadHTMLFiles() to load all templates leads to error // because use templates with same name in different html file will case overridden // so we need to load all templates manually and use multitemplate to render them r := multitemplate.NewRenderer() layoutFiles, err := frontendFS.ReadDir("templates/layouts") if err != nil { log.Logger.Fatal("Error loading layout templates", zap.Error(err)) return } rootFiles, err := frontendFS.ReadDir("templates") if err != nil { log.Logger.Fatal("Error loading root templates", zap.Error(err)) return } for _, rootFile := range rootFiles { if rootFile.IsDir() { continue } name := filepath.Base(rootFile.Name()) templateFiles := []string{"templates/" + name} for _, layout := range layoutFiles { if !layout.IsDir() { templateFiles = append(templateFiles, "templates/layouts/"+layout.Name()) } } r.AddFromFS(name, frontendFS, templateFiles...) } app.HTMLRender = r // Load routes app.GET("/", func(ctx *gin.Context) { monthTop, err := crawler.GetSteam250MonthTop50() if err != nil { ctx.HTML(500, "500.html", err) return } mostPlayed, err := crawler.GetSteam250MostPlayed() if err != nil { ctx.HTML(500, "500.html", err) return } bestOfTheYear, err := crawler.GetSteam250BestOfTheYear() if err != nil { ctx.HTML(500, "500.html", err) return } ctx.HTML(200, "index.html", gin.H{ "MonthTop": monthTop, "MostPlayed": mostPlayed, "BestOfTheYear": bestOfTheYear, }) }) app.GET("/game/:id", func(ctx *gin.Context) { idStr := ctx.Param("id") id, err := primitive.ObjectIDFromHex(idStr) if err != nil { ctx.HTML(400, "400.html", nil) return } info, err := db.GetGameInfoByID(id) if err != nil { ctx.HTML(500, "500.html", err) return } games, err := db.GetGameItemsByIDs(info.GameIDs) if err != nil { ctx.HTML(500, "500.html", err) return } info.Games = games ctx.HTML(200, "game.html", info) }) app.GET("/search", func(ctx *gin.Context) { key := ctx.Query("key") page := ctx.Query("page") key = strings.TrimSpace(key) if len(key) < 2 { ctx.HTML(400, "400.html", errors.New("Search key should be at least 2 characters long")) return } if page == "" { page = "1" } pageInt, err := strconv.Atoi(page) if err != nil { ctx.HTML(400, "400.html", err) return } games, totalPage, err := db.SearchGameInfos(key, pageInt, 10) if err != nil { ctx.HTML(500, "500.html", err) return } res := gin.H{ "Games": games, "TotalPage": totalPage, "CurrentPage": pageInt, "Key": key, } if pageInt > 1 { res["PrevPage"] = pageInt - 1 } if pageInt < totalPage { res["NextPage"] = pageInt + 1 } ctx.HTML(200, "search.html", res) }) } func initApi(app *gin.Engine) { apiGroup := app.Group("/api") GameInfoGroup := apiGroup.Group("/game") GameInfoGroup.Use(middleware.Logger()) GameItemGroup := GameInfoGroup.Group("/raw") GameItemGroup.Use(middleware.Logger()) GameItemGroup.GET("/unorganized", handler.GetUnorganizedGameItemsHandler) GameItemGroup.POST("/organize", middleware.Auth(), handler.OrganizeGameItemHandler) GameItemGroup.GET("/id/:id", handler.GetGameItemByIDHanlder) GameItemGroup.GET("/name/:name", handler.GetGameItemByRawNameHandler) GameItemGroup.GET("/author/:author", handler.GetGameItemsByAuthorHandler) GameInfoGroup.GET("/search", handler.SearchGamesHandler) GameInfoGroup.GET("/name/:name", handler.GetGameInfosByNameHandler) GameInfoGroup.GET("/platform/:platform_type/:platform_id", handler.GetGameInfoByPlatformIDHandler) GameInfoGroup.GET("/id/:id", handler.GetGameInfoByIDHandler) GameInfoGroup.DELETE("/id/:id", middleware.Auth(), handler.DeleteGameInfoHandler) apiGroup.GET("/popular/:type", middleware.Logger(), handler.GetPopularGameInfosHandler) apiGroup.GET("/healthcheck", handler.HealthCheckHandler) apiGroup.GET("/author", middleware.Logger(), handler.GetAllAuthorsHandler) apiGroup.POST("/clean", middleware.Logger(), middleware.Auth(), handler.CleanGameHandler) docs.SwaggerInfo.BasePath = "/api" apiGroup.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) }