package main import ( "log" "a301_server/internal/announcement" "a301_server/internal/auth" "a301_server/internal/chain" "a301_server/internal/download" "a301_server/pkg/config" "a301_server/pkg/database" "a301_server/routes" "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/logger" ) func main() { config.Load() if err := database.ConnectMySQL(); err != nil { log.Fatalf("MySQL 연결 실패: %v", err) } log.Println("MySQL 연결 성공") // AutoMigrate database.DB.AutoMigrate(&auth.User{}, &announcement.Announcement{}, &download.Info{}, &chain.UserWallet{}) if err := database.ConnectRedis(); err != nil { log.Fatalf("Redis 연결 실패: %v", err) } log.Println("Redis 연결 성공") // 의존성 주입 authRepo := auth.NewRepository(database.DB) authSvc := auth.NewService(authRepo, database.RDB) authHandler := auth.NewHandler(authSvc) // 초기 admin 계정 생성 if err := authSvc.EnsureAdmin(config.C.AdminUsername, config.C.AdminPassword); err != nil { log.Printf("admin 계정 생성 실패: %v", err) } else { log.Printf("admin 계정 확인 완료: %s", config.C.AdminUsername) } // Chain (blockchain integration) chainClient := chain.NewClient(config.C.ChainNodeURL) chainRepo := chain.NewRepository(database.DB) chainSvc, err := chain.NewService(chainRepo, chainClient, config.C.ChainID, config.C.OperatorKeyHex, config.C.WalletEncryptionKey) if err != nil { log.Fatalf("chain service init failed: %v", err) } chainHandler := chain.NewHandler(chainSvc) // username → userID 변환 (게임 서버 내부 API용) chainSvc.SetUserResolver(func(username string) (uint, error) { user, err := authRepo.FindByUsername(username) if err != nil { return 0, err } return user.ID, nil }) // 회원가입 시 블록체인 월렛 자동 생성 authSvc.SetWalletCreator(func(userID uint) error { _, err := chainSvc.CreateWallet(userID) return err }) if config.C.InternalAPIKey == "" { log.Println("WARN: INTERNAL_API_KEY not set — /api/internal/* endpoints are disabled") } annRepo := announcement.NewRepository(database.DB) annSvc := announcement.NewService(annRepo) annHandler := announcement.NewHandler(annSvc) dlRepo := download.NewRepository(database.DB) dlSvc := download.NewService(dlRepo, config.C.GameDir) dlHandler := download.NewHandler(dlSvc, config.C.BaseURL) app := fiber.New(fiber.Config{ StreamRequestBody: true, BodyLimit: 4 * 1024 * 1024 * 1024, // 4GB }) app.Use(logger.New()) app.Use(cors.New(cors.Config{ AllowOrigins: "https://a301.tolelom.xyz", AllowHeaders: "Origin, Content-Type, Authorization, Idempotency-Key, X-API-Key", AllowMethods: "GET, POST, PUT, PATCH, DELETE", })) // Rate limiting: 인증 관련 엔드포인트 (로그인/회원가입/리프레시) authLimiter := limiter.New(limiter.Config{ Max: 10, Expiration: 1 * time.Minute, KeyGenerator: func(c *fiber.Ctx) string { return c.IP() }, LimitReached: func(c *fiber.Ctx) error { return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{"error": "요청이 너무 많습니다. 잠시 후 다시 시도해주세요"}) }, }) // Rate limiting: 일반 API apiLimiter := limiter.New(limiter.Config{ Max: 60, Expiration: 1 * time.Minute, KeyGenerator: func(c *fiber.Ctx) string { return c.IP() }, LimitReached: func(c *fiber.Ctx) error { return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{"error": "요청이 너무 많습니다. 잠시 후 다시 시도해주세요"}) }, }) routes.Register(app, authHandler, annHandler, dlHandler, chainHandler, authLimiter, apiLimiter) log.Fatal(app.Listen(":" + config.C.AppPort)) }