feat: Swagger API 문서 추가 + 보스레이드/플레이어 레벨 시스템
Some checks failed
Server CI/CD / lint-and-build (push) Failing after 12m3s
Server CI/CD / deploy (push) Has been cancelled

- swaggo/swag 기반 전체 API 엔드포인트 Swagger 어노테이션 (59개)
- /swagger/ 경로에 Swagger UI 제공
- 보스레이드 데디서버 관리 (등록, 하트비트, 슬롯 리셋)
- 플레이어 레벨/경험치 시스템 및 스탯 성장

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 17:51:37 +09:00
parent ee2cf332fb
commit befea9dd68
19 changed files with 12692 additions and 62 deletions

View File

@@ -20,6 +20,18 @@ func NewHandler(svc *Service) *Handler {
return &Handler{svc: svc}
}
// Register godoc
// @Summary 회원가입
// @Description 새로운 사용자 계정을 생성합니다
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body docs.RegisterRequest true "회원가입 정보"
// @Success 201 {object} docs.MessageResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 409 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/auth/register [post]
func (h *Handler) Register(c *fiber.Ctx) error {
var req struct {
Username string `json:"username"`
@@ -50,6 +62,17 @@ func (h *Handler) Register(c *fiber.Ctx) error {
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "회원가입이 완료되었습니다"})
}
// Login godoc
// @Summary 로그인
// @Description 사용자 인증 후 JWT 토큰을 발급합니다
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body docs.LoginRequest true "로그인 정보"
// @Success 200 {object} docs.LoginResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Router /api/auth/login [post]
func (h *Handler) Login(c *fiber.Ctx) error {
var req struct {
Username string `json:"username"`
@@ -91,6 +114,17 @@ func (h *Handler) Login(c *fiber.Ctx) error {
})
}
// Refresh godoc
// @Summary 토큰 갱신
// @Description Refresh 토큰으로 새 Access 토큰을 발급합니다 (쿠키 또는 body)
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body docs.RefreshRequest false "Refresh 토큰 (쿠키 우선)"
// @Success 200 {object} docs.RefreshResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Router /api/auth/refresh [post]
func (h *Handler) Refresh(c *fiber.Ctx) error {
refreshTokenStr := c.Cookies("refresh_token")
if refreshTokenStr == "" {
@@ -126,6 +160,16 @@ func (h *Handler) Refresh(c *fiber.Ctx) error {
})
}
// Logout godoc
// @Summary 로그아웃
// @Description 현재 세션을 무효화합니다
// @Tags Auth
// @Produce json
// @Security BearerAuth
// @Success 200 {object} docs.MessageResponse
// @Failure 401 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/auth/logout [post]
func (h *Handler) Logout(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
if !ok {
@@ -146,6 +190,19 @@ func (h *Handler) Logout(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "로그아웃 되었습니다"})
}
// GetAllUsers godoc
// @Summary 전체 유저 목록 (관리자)
// @Description 모든 유저 목록을 조회합니다
// @Tags Users
// @Produce json
// @Security BearerAuth
// @Param offset query int false "시작 위치" default(0)
// @Param limit query int false "조회 수" default(50)
// @Success 200 {array} docs.UserResponse
// @Failure 401 {object} docs.ErrorResponse
// @Failure 403 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/users/ [get]
func (h *Handler) GetAllUsers(c *fiber.Ctx) error {
offset := c.QueryInt("offset", 0)
limit := c.QueryInt("limit", 50)
@@ -162,6 +219,21 @@ func (h *Handler) GetAllUsers(c *fiber.Ctx) error {
return c.JSON(users)
}
// UpdateRole godoc
// @Summary 유저 권한 변경 (관리자)
// @Description 유저의 역할을 admin 또는 user로 변경합니다
// @Tags Users
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "유저 ID"
// @Param body body docs.UpdateRoleRequest true "변경할 역할"
// @Success 200 {object} docs.MessageResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Failure 403 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/users/{id}/role [patch]
func (h *Handler) UpdateRole(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {
@@ -182,6 +254,18 @@ func (h *Handler) UpdateRole(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "권한이 변경되었습니다"})
}
// VerifyToken godoc
// @Summary 토큰 검증 (내부 API)
// @Description JWT 토큰을 검증하고 username을 반환합니다
// @Tags Internal - Auth
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Param body body docs.VerifyTokenRequest true "검증할 토큰"
// @Success 200 {object} docs.VerifyTokenResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Router /api/internal/auth/verify [post]
func (h *Handler) VerifyToken(c *fiber.Ctx) error {
var req struct {
Token string `json:"token"`
@@ -200,6 +284,14 @@ func (h *Handler) VerifyToken(c *fiber.Ctx) error {
})
}
// SSAFYLoginURL godoc
// @Summary SSAFY 로그인 URL
// @Description SSAFY OAuth 로그인 URL을 생성합니다
// @Tags Auth
// @Produce json
// @Success 200 {object} docs.SSAFYLoginURLResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/auth/ssafy/login [get]
func (h *Handler) SSAFYLoginURL(c *fiber.Ctx) error {
loginURL, err := h.svc.GetSSAFYLoginURL()
if err != nil {
@@ -208,6 +300,17 @@ func (h *Handler) SSAFYLoginURL(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"url": loginURL})
}
// SSAFYCallback godoc
// @Summary SSAFY OAuth 콜백
// @Description SSAFY 인가 코드를 교환하여 로그인합니다
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body docs.SSAFYCallbackRequest true "인가 코드"
// @Success 200 {object} docs.LoginResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Router /api/auth/ssafy/callback [post]
func (h *Handler) SSAFYCallback(c *fiber.Ctx) error {
var req struct {
Code string `json:"code"`
@@ -242,8 +345,16 @@ func (h *Handler) SSAFYCallback(c *fiber.Ctx) error {
})
}
// CreateLaunchTicket issues a one-time ticket for the game launcher.
// The launcher uses this ticket instead of receiving the JWT directly in the URL.
// CreateLaunchTicket godoc
// @Summary 런처 티켓 발급
// @Description 게임 런처용 일회성 티켓을 발급합니다
// @Tags Auth
// @Produce json
// @Security BearerAuth
// @Success 200 {object} docs.LaunchTicketResponse
// @Failure 401 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/auth/launch-ticket [post]
func (h *Handler) CreateLaunchTicket(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
if !ok {
@@ -256,8 +367,17 @@ func (h *Handler) CreateLaunchTicket(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"ticket": ticket})
}
// RedeemLaunchTicket exchanges a one-time ticket for an access token.
// Called by the game launcher, not the web browser.
// RedeemLaunchTicket godoc
// @Summary 런처 티켓 교환
// @Description 일회성 티켓을 Access 토큰으로 교환합니다
// @Tags Auth
// @Accept json
// @Produce json
// @Param body body docs.RedeemTicketRequest true "티켓"
// @Success 200 {object} docs.RedeemTicketResponse
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Router /api/auth/redeem-ticket [post]
func (h *Handler) RedeemLaunchTicket(c *fiber.Ctx) error {
var req struct {
Ticket string `json:"ticket"`
@@ -273,6 +393,18 @@ func (h *Handler) RedeemLaunchTicket(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"token": token})
}
// DeleteUser godoc
// @Summary 유저 삭제 (관리자)
// @Description 유저를 삭제합니다
// @Tags Users
// @Security BearerAuth
// @Param id path int true "유저 ID"
// @Success 204
// @Failure 400 {object} docs.ErrorResponse
// @Failure 401 {object} docs.ErrorResponse
// @Failure 403 {object} docs.ErrorResponse
// @Failure 500 {object} docs.ErrorResponse
// @Router /api/users/{id} [delete]
func (h *Handler) DeleteUser(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil {