feat: Swagger API 문서 추가 + 보스레이드/플레이어 레벨 시스템
- 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:
@@ -19,8 +19,18 @@ func bossError(c *fiber.Ctx, status int, userMsg string, err error) error {
|
||||
return c.Status(status).JSON(fiber.Map{"error": userMsg})
|
||||
}
|
||||
|
||||
// RequestEntry handles POST /api/internal/bossraid/entry
|
||||
// Called by MMO server when a party requests boss raid entry.
|
||||
// RequestEntry godoc
|
||||
// @Summary 보스 레이드 입장 요청 (내부 API)
|
||||
// @Description MMO 서버에서 파티의 보스 레이드 입장을 요청합니다. 모든 플레이어의 entry token을 반환합니다.
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.RequestEntryRequest true "입장 정보"
|
||||
// @Success 201 {object} docs.InternalRequestEntryResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 409 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/entry [post]
|
||||
func (h *Handler) RequestEntry(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
Usernames []string `json:"usernames"`
|
||||
@@ -38,7 +48,7 @@ func (h *Handler) RequestEntry(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
room, err := h.svc.RequestEntry(req.Usernames, req.BossID)
|
||||
room, tokens, err := h.svc.RequestEntryWithTokens(req.Usernames, req.BossID)
|
||||
if err != nil {
|
||||
return bossError(c, fiber.StatusConflict, "보스 레이드 입장에 실패했습니다", err)
|
||||
}
|
||||
@@ -49,11 +59,21 @@ func (h *Handler) RequestEntry(c *fiber.Ctx) error {
|
||||
"bossId": room.BossID,
|
||||
"players": req.Usernames,
|
||||
"status": room.Status,
|
||||
"tokens": tokens,
|
||||
})
|
||||
}
|
||||
|
||||
// StartRaid handles POST /api/internal/bossraid/start
|
||||
// Called by dedicated server when the Fusion session begins.
|
||||
// StartRaid godoc
|
||||
// @Summary 레이드 시작 (내부 API)
|
||||
// @Description Fusion 세션이 시작될 때 데디케이티드 서버에서 호출합니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.SessionNameRequest true "세션 정보"
|
||||
// @Success 200 {object} docs.RoomStatusResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/start [post]
|
||||
func (h *Handler) StartRaid(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
SessionName string `json:"sessionName"`
|
||||
@@ -77,8 +97,18 @@ func (h *Handler) StartRaid(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// CompleteRaid handles POST /api/internal/bossraid/complete
|
||||
// Called by dedicated server when the boss is killed. Distributes rewards.
|
||||
// CompleteRaid godoc
|
||||
// @Summary 레이드 완료 (내부 API)
|
||||
// @Description 보스 처치 시 데디케이티드 서버에서 호출합니다. 보상을 분배합니다.
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param Idempotency-Key header string true "멱등성 키"
|
||||
// @Param body body docs.CompleteRaidRequest true "완료 정보 및 보상"
|
||||
// @Success 200 {object} docs.CompleteRaidResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/complete [post]
|
||||
func (h *Handler) CompleteRaid(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
SessionName string `json:"sessionName"`
|
||||
@@ -104,8 +134,17 @@ func (h *Handler) CompleteRaid(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// FailRaid handles POST /api/internal/bossraid/fail
|
||||
// Called by dedicated server on timeout or party wipe.
|
||||
// FailRaid godoc
|
||||
// @Summary 레이드 실패 (내부 API)
|
||||
// @Description 타임아웃 또는 전멸 시 데디케이티드 서버에서 호출합니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.SessionNameRequest true "세션 정보"
|
||||
// @Success 200 {object} docs.RoomStatusResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/fail [post]
|
||||
func (h *Handler) FailRaid(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
SessionName string `json:"sessionName"`
|
||||
@@ -129,9 +168,20 @@ func (h *Handler) FailRaid(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// RequestEntryAuth handles POST /api/bossraid/entry (JWT authenticated).
|
||||
// Called by the game client to request boss raid entry.
|
||||
// The authenticated user must be included in the usernames list.
|
||||
// RequestEntryAuth godoc
|
||||
// @Summary 보스 레이드 입장 요청
|
||||
// @Description 게임 클라이언트에서 보스 레이드 입장을 요청합니다. 인증된 유저가 입장 목록에 포함되어야 합니다.
|
||||
// @Tags Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param body body docs.RequestEntryAuthRequest true "입장 정보"
|
||||
// @Success 201 {object} docs.RequestEntryResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 401 {object} docs.ErrorResponse
|
||||
// @Failure 403 {object} docs.ErrorResponse
|
||||
// @Failure 409 {object} docs.ErrorResponse
|
||||
// @Router /api/bossraid/entry [post]
|
||||
func (h *Handler) RequestEntryAuth(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
Usernames []string `json:"usernames"`
|
||||
@@ -188,9 +238,16 @@ func (h *Handler) RequestEntryAuth(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetMyEntryToken handles GET /api/bossraid/my-entry-token (JWT authenticated).
|
||||
// Returns the pending entry token for the authenticated user.
|
||||
// Called by party members after the leader requests entry.
|
||||
// GetMyEntryToken godoc
|
||||
// @Summary 내 입장 토큰 조회
|
||||
// @Description 현재 유저의 대기 중인 입장 토큰을 조회합니다
|
||||
// @Tags Boss Raid
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Success 200 {object} docs.MyEntryTokenResponse
|
||||
// @Failure 401 {object} docs.ErrorResponse
|
||||
// @Failure 404 {object} docs.ErrorResponse
|
||||
// @Router /api/bossraid/my-entry-token [get]
|
||||
func (h *Handler) GetMyEntryToken(c *fiber.Ctx) error {
|
||||
username, _ := c.Locals("username").(string)
|
||||
if username == "" {
|
||||
@@ -208,9 +265,18 @@ func (h *Handler) GetMyEntryToken(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// ValidateEntryToken handles POST /api/internal/bossraid/validate-entry (ServerAuth).
|
||||
// Called by the dedicated server to validate a player's entry token.
|
||||
// Consumes the token (one-time use).
|
||||
// ValidateEntryToken godoc
|
||||
// @Summary 입장 토큰 검증 (내부 API)
|
||||
// @Description 데디케이티드 서버에서 플레이어의 입장 토큰을 검증합니다. 일회성 소모.
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.ValidateEntryTokenRequest true "토큰"
|
||||
// @Success 200 {object} docs.ValidateEntryTokenResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 401 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/validate-entry [post]
|
||||
func (h *Handler) ValidateEntryToken(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
EntryToken string `json:"entryToken"`
|
||||
@@ -237,8 +303,17 @@ func (h *Handler) ValidateEntryToken(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetRoom handles GET /api/internal/bossraid/room
|
||||
// Query param: sessionName
|
||||
// GetRoom godoc
|
||||
// @Summary 방 정보 조회 (내부 API)
|
||||
// @Description sessionName으로 보스 레이드 방 정보를 조회합니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param sessionName query string true "세션 이름"
|
||||
// @Success 200 {object} bossraid.BossRoom
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 404 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/room [get]
|
||||
func (h *Handler) GetRoom(c *fiber.Ctx) error {
|
||||
sessionName := c.Query("sessionName")
|
||||
if sessionName == "" {
|
||||
@@ -252,3 +327,127 @@ func (h *Handler) GetRoom(c *fiber.Ctx) error {
|
||||
|
||||
return c.JSON(room)
|
||||
}
|
||||
|
||||
// RegisterServer godoc
|
||||
// @Summary 데디케이티드 서버 등록 (내부 API)
|
||||
// @Description 데디케이티드 서버 컨테이너가 시작 시 호출합니다. 룸 슬롯을 자동 할당합니다.
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.RegisterServerRequest true "서버 정보"
|
||||
// @Success 201 {object} docs.RegisterServerResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 409 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/register [post]
|
||||
func (h *Handler) RegisterServer(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
ServerName string `json:"serverName"`
|
||||
InstanceID string `json:"instanceId"`
|
||||
MaxRooms int `json:"maxRooms"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
|
||||
}
|
||||
if req.ServerName == "" || req.InstanceID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "serverName과 instanceId는 필수입니다"})
|
||||
}
|
||||
|
||||
sessionName, err := h.svc.RegisterServer(req.ServerName, req.InstanceID, req.MaxRooms)
|
||||
if err != nil {
|
||||
return bossError(c, fiber.StatusConflict, "서버 등록에 실패했습니다", err)
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
"sessionName": sessionName,
|
||||
"instanceId": req.InstanceID,
|
||||
})
|
||||
}
|
||||
|
||||
// Heartbeat godoc
|
||||
// @Summary 하트비트 (내부 API)
|
||||
// @Description 데디케이티드 서버 컨테이너가 주기적으로 호출합니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.HeartbeatRequest true "인스턴스 정보"
|
||||
// @Success 200 {object} docs.StatusResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 404 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/heartbeat [post]
|
||||
func (h *Handler) Heartbeat(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
|
||||
}
|
||||
if req.InstanceID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "instanceId는 필수입니다"})
|
||||
}
|
||||
|
||||
if err := h.svc.Heartbeat(req.InstanceID); err != nil {
|
||||
return bossError(c, fiber.StatusNotFound, "인스턴스를 찾을 수 없습니다", err)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "ok"})
|
||||
}
|
||||
|
||||
// ResetRoom godoc
|
||||
// @Summary 룸 슬롯 리셋 (내부 API)
|
||||
// @Description 레이드 종료 후 슬롯을 idle 상태로 되돌립니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param body body docs.ResetRoomRequest true "세션 정보"
|
||||
// @Success 200 {object} docs.ResetRoomResponse
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 500 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/reset-room [post]
|
||||
func (h *Handler) ResetRoom(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
SessionName string `json:"sessionName"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
|
||||
}
|
||||
if req.SessionName == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "sessionName은 필수입니다"})
|
||||
}
|
||||
|
||||
if err := h.svc.ResetRoom(req.SessionName); err != nil {
|
||||
return bossError(c, fiber.StatusInternalServerError, "슬롯 리셋에 실패했습니다", err)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "ok", "sessionName": req.SessionName})
|
||||
}
|
||||
|
||||
// GetServerStatus godoc
|
||||
// @Summary 서버 상태 조회 (내부 API)
|
||||
// @Description 데디케이티드 서버의 정보와 룸 슬롯 목록을 조회합니다
|
||||
// @Tags Internal - Boss Raid
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param serverName query string true "서버 이름"
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} docs.ErrorResponse
|
||||
// @Failure 404 {object} docs.ErrorResponse
|
||||
// @Router /api/internal/bossraid/server-status [get]
|
||||
func (h *Handler) GetServerStatus(c *fiber.Ctx) error {
|
||||
serverName := c.Query("serverName")
|
||||
if serverName == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "serverName은 필수입니다"})
|
||||
}
|
||||
|
||||
server, slots, err := h.svc.GetServerStatus(serverName)
|
||||
if err != nil {
|
||||
return bossError(c, fiber.StatusNotFound, "서버를 찾을 수 없습니다", err)
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"server": server,
|
||||
"slots": slots,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user