- 5개 핸들러 err.Error() → 제네릭 메시지 (Login, Refresh, SSAFY, Ticket, BossRaid) - Redis context.Background() → WithTimeout 5s (10곳) - SprintMultiplier 범위 검증 추가 - 방어적 문서화 (SSAFY 충돌, zip bomb, body limit prefix, 로그 주입) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
105 lines
3.3 KiB
Go
105 lines
3.3 KiB
Go
package player
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type Handler struct {
|
|
svc *Service
|
|
}
|
|
|
|
func NewHandler(svc *Service) *Handler {
|
|
return &Handler{svc: svc}
|
|
}
|
|
|
|
// GetProfile 자신의 프로필 조회 (JWT 인증)
|
|
func (h *Handler) GetProfile(c *fiber.Ctx) error {
|
|
userID, ok := c.Locals("userID").(uint)
|
|
if !ok {
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "인증 정보가 올바르지 않습니다"})
|
|
}
|
|
|
|
profile, err := h.svc.GetProfile(userID)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(profile)
|
|
}
|
|
|
|
// UpdateProfile 자신의 프로필 수정 (JWT 인증)
|
|
func (h *Handler) UpdateProfile(c *fiber.Ctx) error {
|
|
userID, ok := c.Locals("userID").(uint)
|
|
if !ok {
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "인증 정보가 올바르지 않습니다"})
|
|
}
|
|
|
|
var req struct {
|
|
Nickname string `json:"nickname"`
|
|
}
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
|
|
}
|
|
|
|
req.Nickname = strings.TrimSpace(req.Nickname)
|
|
if req.Nickname != "" {
|
|
nicknameRunes := []rune(req.Nickname)
|
|
if len(nicknameRunes) < 2 || len(nicknameRunes) > 30 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "닉네임은 2~30자여야 합니다"})
|
|
}
|
|
for _, r := range nicknameRunes {
|
|
if unicode.IsControl(r) {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "닉네임에 허용되지 않는 문자가 포함되어 있습니다"})
|
|
}
|
|
}
|
|
}
|
|
|
|
profile, err := h.svc.UpdateProfile(userID, req.Nickname)
|
|
if err != nil {
|
|
log.Printf("프로필 수정 실패 (userID=%d): %v", userID, err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "서버 오류가 발생했습니다"})
|
|
}
|
|
|
|
return c.JSON(profile)
|
|
}
|
|
|
|
// InternalGetProfile 내부 API: username 쿼리 파라미터로 프로필 조회
|
|
func (h *Handler) InternalGetProfile(c *fiber.Ctx) error {
|
|
username := c.Query("username")
|
|
if username == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "username 파라미터가 필요합니다"})
|
|
}
|
|
|
|
profile, err := h.svc.GetProfileByUsername(username)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": err.Error()})
|
|
}
|
|
|
|
return c.JSON(profile)
|
|
}
|
|
|
|
// InternalSaveGameData 내부 API: username 쿼리 파라미터로 게임 데이터 저장
|
|
func (h *Handler) InternalSaveGameData(c *fiber.Ctx) error {
|
|
username := c.Query("username")
|
|
if username == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "username 파라미터가 필요합니다"})
|
|
}
|
|
|
|
var req GameDataRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "잘못된 요청입니다"})
|
|
}
|
|
|
|
if err := h.svc.SaveGameDataByUsername(username, &req); err != nil {
|
|
// Username from internal API (ServerAuth protected) — low risk of injection
|
|
log.Printf("게임 데이터 저장 실패 (username=%s): %v", username, err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "서버 오류가 발생했습니다"})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{"message": "게임 데이터가 저장되었습니다"})
|
|
}
|