package main import ( "context" "database/sql" "fmt" "log" "net" "os" "path/filepath" walletpb "game-admin/gen" "game-admin/internal/config" "game-admin/internal/grpcserver" "game-admin/internal/handlers" "game-admin/internal/middleware" "game-admin/internal/migrations" "game-admin/internal/models" "game-admin/internal/wallet" "github.com/gin-gonic/gin" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" "github.com/uptrace/bun/driver/pgdriver" "github.com/uptrace/bun/extra/bundebug" "google.golang.org/grpc" ) func main() { cfg := config.Load() dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", cfg.DBUser, cfg.DBPassword, cfg.DBHost, cfg.DBPort, cfg.DBName) connector := pgdriver.NewConnector(pgdriver.WithDSN(dsn)) sqldb := sql.OpenDB(connector) db := bun.NewDB(sqldb, pgdialect.New()) db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) if err := os.MkdirAll(filepath.Join(cfg.UploadDir, "images"), 0o755); err != nil { log.Fatal("Upload directory init failed:", err) } if err := migrations.Migrate(context.Background(), db, cfg); err != nil { log.Fatal("Migration failed:", err) } authH := handlers.NewAuthHandler(db, cfg) userH := handlers.NewUserHandler(db, cfg) gameH := handlers.NewGameHandler(db, cfg) inviteH := handlers.NewInviteHandler(db, cfg) languageH := handlers.NewLanguageHandler(db, cfg) imageH := handlers.NewImageHandler(cfg) walletService := wallet.NewService(db) r := gin.Default() r.Static("/uploads", cfg.UploadDir) r.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) api := r.Group("/api") { api.POST("/auth/login", authH.Login) api.POST("/auth/register", authH.Register) api.GET("/invites/validate/:code", inviteH.Validate) } auth := api.Group("", middleware.AuthRequired(cfg)) { auth.GET("/auth/me", authH.Me) auth.GET("/games", gameH.List) auth.GET("/games/:id", gameH.GetByID) auth.GET("/modules", gameH.ListModules) auth.GET("/languages", languageH.List) auth.GET("/languages/:id", languageH.GetByID) auth.GET("/games/:id/leaderboards", gameH.ListLeaderboards) auth.GET("/games/:id/leaderboards/:leaderboard_id", gameH.GetLeaderboard) auth.GET("/leaderboards/:leaderboard_id/groups", gameH.ListLeaderboardGroups) auth.GET("/leaderboards/:leaderboard_id/rankings", gameH.GetLeaderboardRankings) auth.GET("/games/:id/notifications", gameH.ListNotifications) auth.GET("/games/:id/notifications/:notification_id", gameH.GetNotification) admin := auth.Group("", middleware.RoleRequired(models.RoleAdmin, models.RoleModerator)) { admin.GET("/users", userH.List) admin.GET("/users/:id", userH.GetByID) admin.GET("/games/:id/users", userH.ListByGame) admin.PATCH("/users/:id/role", userH.UpdateRole) admin.PATCH("/users/:id/toggle-active", userH.ToggleActive) admin.POST("/games", gameH.Create) admin.PUT("/games/:id", gameH.Update) admin.DELETE("/games/:id", gameH.Delete) admin.GET("/games/:id/modules", gameH.ListGameModules) admin.POST("/games/:id/modules", gameH.ConnectModule) admin.DELETE("/games/:id/modules/:module_key", gameH.DisconnectModule) admin.POST("/games/:id/leaderboards", gameH.CreateLeaderboard) admin.PUT("/games/:id/leaderboards/:leaderboard_id", gameH.UpdateLeaderboard) admin.DELETE("/games/:id/leaderboards/:leaderboard_id", gameH.DeleteLeaderboard) admin.POST("/leaderboards/:leaderboard_id/groups", gameH.CreateLeaderboardGroup) admin.PUT("/leaderboard-groups/:group_id", gameH.UpdateLeaderboardGroup) admin.DELETE("/leaderboard-groups/:group_id", gameH.DeleteLeaderboardGroup) admin.POST("/leaderboard-groups/:group_id/members", gameH.AddLeaderboardGroupMember) admin.DELETE("/leaderboard-groups/:group_id/members/:user_id", gameH.DeleteLeaderboardGroupMember) admin.POST("/leaderboards/:leaderboard_id/scores", gameH.UpsertLeaderboardScore) admin.POST("/games/:id/notifications", gameH.CreateNotification) admin.PUT("/games/:id/notifications/:notification_id", gameH.UpdateNotification) admin.DELETE("/games/:id/notifications/:notification_id", gameH.DeleteNotification) admin.GET("/games/:id/variables", gameH.ListVariables) admin.POST("/games/:id/variables", gameH.CreateVariable) admin.PUT("/games/:id/variables/:var_id", gameH.UpdateVariable) admin.DELETE("/games/:id/variables/:var_id", gameH.DeleteVariable) admin.POST("/images", imageH.Upload) admin.POST("/languages", languageH.Create) admin.PUT("/languages/:id", languageH.Update) admin.DELETE("/languages/:id", languageH.Delete) admin.GET("/users/:id/games", gameH.UserGames) admin.POST("/users/:id/games", gameH.ConnectGame) admin.DELETE("/users/:id/games/:game_id", gameH.DisconnectGame) admin.POST("/user-games/:ug_id/topup", gameH.TopUp) admin.GET("/user-games/:ug_id/transactions", gameH.Transactions) admin.POST("/invites", inviteH.Create) admin.GET("/invites", inviteH.List) admin.DELETE("/invites/:code", inviteH.Delete) } } grpcListener, err := net.Listen("tcp", ":"+cfg.GRPCPort) if err != nil { log.Fatal("gRPC listen error:", err) } grpcSrv := grpc.NewServer( grpc.UnaryInterceptor(grpcserver.HMACUnaryServerInterceptor(cfg.GRPCHMACSecret)), ) walletpb.RegisterWalletServiceServer(grpcSrv, grpcserver.NewWalletServer(walletService)) go func() { log.Printf("gRPC server starting on :%s", cfg.GRPCPort) if err := grpcSrv.Serve(grpcListener); err != nil { log.Fatal("gRPC serve error:", err) } }() log.Printf("HTTP server starting on :%s", cfg.ServerPort) if err := r.Run(":" + cfg.ServerPort); err != nil { log.Fatal(err) } }