All checks were successful
Server CI/CD / deploy (push) Successful in 1m36s
- unsafe 타입 단언 → safe assertion (chain handler 11곳, auth Logout) - Repository 에러 시 nil 반환으로 통일 (chain, auth, announcement) - string ID → uint 파싱으로 타입 안전성 확보 (auth, announcement) - CORS AllowHeaders에 Idempotency-Key, X-API-Key 추가 - /verify 엔드포인트 rate limiter 적용 - Redis 호출에 context timeout 적용 (auth, idempotency 미들웨어) - chain handler 에러 응답에서 내부 정보 노출 방지 - f.Close() 에러 검사 추가 (download service 2곳) - 공지사항 Delete 404 응답 추가 - 회원가입 롤백 시 Delete 에러 로깅 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
124 lines
3.7 KiB
Go
124 lines
3.7 KiB
Go
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))
|
|
}
|