Skip to content

Commit 28ed87d

Browse files
authored
[Player] Player 레이어드 구조 리펙토링 (#163)
* refactor: 디렉토리 위치 변경 * refactor(PlayerDetail): player details 정보 레이어 분리 * refactor(PlayerProfile): 플레이어 Profile 조회 리펙토링 * refactor: port 패키지 위치 변경
1 parent 0d0e778 commit 28ed87d

File tree

110 files changed

+1066
-792
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+1066
-792
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.codequistify.master.application.exception;
2+
3+
import org.codequistify.master.global.exception.ErrorCode;
4+
import org.codequistify.master.global.exception.domain.BusinessException;
5+
import org.springframework.http.HttpStatus;
6+
7+
public class ApplicationException extends RuntimeException {
8+
private final HttpStatus httpStatus;
9+
private final ErrorCode errorCode;
10+
private final String detail;
11+
12+
public ApplicationException(ErrorCode errorCode, HttpStatus httpStatus) {
13+
super(errorCode.getMessage());
14+
this.httpStatus = httpStatus;
15+
this.errorCode = errorCode;
16+
this.detail = "";
17+
}
18+
19+
public ApplicationException(ErrorCode errorCode, HttpStatus httpStatus, String detail) {
20+
super(errorCode.getMessage());
21+
this.httpStatus = httpStatus;
22+
this.errorCode = errorCode;
23+
this.detail = detail;
24+
}
25+
26+
public ApplicationException(ErrorCode errorCode, HttpStatus httpStatus, Throwable cause) {
27+
super(errorCode.getMessage(), cause);
28+
this.httpStatus = httpStatus;
29+
this.errorCode = errorCode;
30+
this.detail = cause.getMessage();
31+
}
32+
33+
public ApplicationException(HttpStatus httpStatus, Throwable cause) {
34+
super(ErrorCode.UNKNOWN.getMessage(), cause);
35+
this.httpStatus = httpStatus;
36+
this.errorCode = ErrorCode.UNKNOWN;
37+
this.detail = cause.getMessage();
38+
}
39+
40+
public ApplicationException(BusinessException businessException, HttpStatus httpStatus) {
41+
this.httpStatus = httpStatus;
42+
this.errorCode = businessException.getErrorCode();
43+
this.detail = businessException.getDetail();
44+
}
45+
46+
public ApplicationException(ErrorCode errorCode, HttpStatus httpStatus, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
47+
super(errorCode.getMessage(), cause, enableSuppression, writableStackTrace);
48+
this.httpStatus = httpStatus;
49+
this.errorCode = errorCode;
50+
this.detail = cause.getMessage();
51+
}
52+
53+
public ApplicationException(RuntimeException exception) {
54+
if (exception.getClass() == BusinessException.class) {
55+
BusinessException businessException = (BusinessException) exception;
56+
this.httpStatus = businessException.getHttpStatus();
57+
this.errorCode = businessException.getErrorCode();
58+
this.detail = businessException.getMessage();
59+
} else {
60+
this.httpStatus = HttpStatus.BAD_REQUEST;
61+
this.errorCode = ErrorCode.UNKNOWN;
62+
this.detail = exception.getMessage();
63+
}
64+
}
65+
}

src/main/java/org/codequistify/master/domain/player/controller/PlayerDetailsController.java renamed to src/main/java/org/codequistify/master/application/player/controller/PlayerDetailsController.java

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
package org.codequistify.master.domain.player.controller;
1+
package org.codequistify.master.application.player.controller;
22

33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.tags.Tag;
55
import lombok.RequiredArgsConstructor;
6-
import org.codequistify.master.domain.player.domain.Player;
7-
import org.codequistify.master.domain.player.dto.UpdatePasswordRequest;
8-
import org.codequistify.master.domain.player.service.PlayerDetailsService;
6+
import org.codequistify.master.application.exception.ApplicationException;
7+
import org.codequistify.master.application.player.service.PlayerDetailsService;
8+
import org.codequistify.master.application.player.dto.UpdatePasswordRequest;
9+
import org.codequistify.master.core.domain.player.model.Player;
910
import org.codequistify.master.global.aspect.LogMonitoring;
1011
import org.codequistify.master.global.exception.ErrorCode;
11-
import org.codequistify.master.global.exception.domain.BusinessException;
1212
import org.codequistify.master.global.util.BasicResponse;
1313
import org.slf4j.Logger;
1414
import org.slf4j.LoggerFactory;
@@ -22,9 +22,9 @@
2222
@Tag(name = "Player")
2323
@RequestMapping("api/players")
2424
public class PlayerDetailsController {
25-
private final PlayerDetailsService playerDetailsService;
2625

27-
private final Logger LOGGER = LoggerFactory.getLogger(PlayerDetailsController.class);
26+
private final PlayerDetailsService playerDetailsService;
27+
private final Logger logger = LoggerFactory.getLogger(PlayerDetailsController.class);
2828

2929
@Operation(
3030
summary = "비밀번호 재설정",
@@ -34,13 +34,10 @@ public class PlayerDetailsController {
3434
public ResponseEntity<BasicResponse> updatePassword(@AuthenticationPrincipal Player player,
3535
@RequestBody UpdatePasswordRequest request) {
3636
playerDetailsService.updatePassword(player, request);
37-
38-
LOGGER.info("[updatePassword] 비밀번호 재설정 완료, Player: {}", player.getUid());
39-
BasicResponse response = BasicResponse.of("SUCCESS");
40-
return ResponseEntity.status(HttpStatus.OK).body(response);
37+
logger.info("[updatePassword] 비밀번호 재설정 완료, Player: {}", player.getUid());
38+
return ResponseEntity.ok(BasicResponse.of("SUCCESS"));
4139
}
4240

43-
4441
@Operation(
4542
summary = "비밀번호 초기화",
4643
description = "비밀번호를 초기화합니다. password에 새로운 비밀번호를 담아 보냅니다.")
@@ -49,10 +46,8 @@ public ResponseEntity<BasicResponse> updatePassword(@AuthenticationPrincipal Pla
4946
public ResponseEntity<BasicResponse> resetPassword(@AuthenticationPrincipal Player player,
5047
@RequestBody UpdatePasswordRequest request) {
5148
playerDetailsService.resetPassword(player, request);
52-
53-
LOGGER.info("[resetPassword] 비밀번호 초기화 완료, Player: {}", player.getUid());
54-
BasicResponse response = BasicResponse.of("SUCCESS");
55-
return ResponseEntity.status(HttpStatus.OK).body(response);
49+
logger.info("[resetPassword] 비밀번호 초기화 완료, Player: {}", player.getUid());
50+
return ResponseEntity.ok(BasicResponse.of("SUCCESS"));
5651
}
5752

5853
@Operation(
@@ -62,18 +57,11 @@ public ResponseEntity<BasicResponse> resetPassword(@AuthenticationPrincipal Play
6257
@DeleteMapping("{uid}")
6358
public ResponseEntity<BasicResponse> deletePlayer(@AuthenticationPrincipal Player player,
6459
@PathVariable String uid) {
65-
if (!uid.equals(player.getUid())) {
66-
throw new BusinessException(ErrorCode.PLAYER_NOT_FOUND, HttpStatus.NOT_FOUND);
60+
if (!player.getUid().getValue().equals(uid)) {
61+
throw new ApplicationException(ErrorCode.PLAYER_NOT_FOUND, HttpStatus.NOT_FOUND);
6762
}
68-
6963
playerDetailsService.deletePlayer(player);
70-
71-
LOGGER.info("[deletePlayer] 계정 삭제 완료, Player: {}", player.getUid());
72-
BasicResponse response = BasicResponse.of("SUCCESS");
73-
return ResponseEntity.status(HttpStatus.OK).body(response);
64+
logger.info("[deletePlayer] 계정 삭제 완료, Player: {}", player.getUid());
65+
return ResponseEntity.ok(BasicResponse.of("SUCCESS"));
7466
}
75-
76-
//전화번호 인증
77-
78-
7967
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.codequistify.master.application.player.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.tags.Tag;
5+
import lombok.RequiredArgsConstructor;
6+
import org.codequistify.master.application.player.dto.PlayerProfile;
7+
import org.codequistify.master.application.player.dto.PlayerStageProgressResponse;
8+
import org.codequistify.master.application.player.service.PlayerProfileService;
9+
import org.codequistify.master.application.player.service.PlayerQueryService;
10+
import org.codequistify.master.core.domain.player.model.Player;
11+
import org.codequistify.master.core.domain.stage.domain.CompletedStatus;
12+
import org.codequistify.master.core.domain.stage.dto.HeatMapDataPoint;
13+
import org.codequistify.master.global.aspect.LogMonitoring;
14+
import org.codequistify.master.global.exception.ErrorCode;
15+
import org.codequistify.master.global.exception.domain.BusinessException;
16+
import org.codequistify.master.global.util.BasicResponse;
17+
import org.springframework.http.HttpStatus;
18+
import org.springframework.http.ResponseEntity;
19+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
20+
import org.springframework.web.bind.annotation.GetMapping;
21+
import org.springframework.web.bind.annotation.RequestMapping;
22+
import org.springframework.web.bind.annotation.RequestParam;
23+
import org.springframework.web.bind.annotation.RestController;
24+
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
28+
@RestController
29+
@RequiredArgsConstructor
30+
@RequestMapping("/api/players")
31+
@Tag(name = "Player")
32+
public class PlayerProfileController {
33+
34+
private final PlayerProfileService playerProfileService;
35+
private final PlayerQueryService playerQueryService;
36+
37+
@LogMonitoring
38+
@GetMapping("")
39+
public ResponseEntity<List<PlayerProfile>> getAllPlayerProfiles() {
40+
List<PlayerProfile> response = playerProfileService.findAllPlayerProfiles();
41+
return ResponseEntity.ok(response);
42+
}
43+
44+
@Operation(
45+
summary = "자신의 profile 조회",
46+
description = "현재 로그인된 사용자의 프로필 정보를 조회합니다."
47+
)
48+
@LogMonitoring
49+
@GetMapping("me/profile")
50+
public ResponseEntity<PlayerProfile> getMyProfile(@AuthenticationPrincipal Player principal) {
51+
Player player = playerQueryService.findOneByUid(principal.getUid());
52+
return ResponseEntity.ok(PlayerProfile.from(player));
53+
}
54+
55+
@Operation(
56+
summary = "자신의 문제풀이 상태를 조회",
57+
description = """
58+
자신의 문제풀이 상태를 조회할 수 있다.
59+
NOT_COMPLETED는 허용하지 않는다.
60+
URI 정보는 현재 제공되지 않는다.
61+
"""
62+
)
63+
@LogMonitoring
64+
@GetMapping("me/stages")
65+
public ResponseEntity<PlayerStageProgressResponse> getStagesByStatusForPlayer(
66+
@AuthenticationPrincipal Player principal,
67+
@RequestParam CompletedStatus status
68+
) {
69+
if (status == CompletedStatus.NOT_COMPLETED) {
70+
throw new BusinessException(ErrorCode.INVALID_SEARCH_CRITERIA, HttpStatus.BAD_REQUEST);
71+
}
72+
73+
Player player = playerQueryService.findOneByUid(principal.getUid());
74+
PlayerStageProgressResponse response = switch (status) {
75+
case COMPLETED -> playerProfileService.getCompletedStagesByPlayerId(player);
76+
case IN_PROGRESS -> playerProfileService.getInProgressStagesByPlayerId(player);
77+
default -> new PlayerStageProgressResponse(new ArrayList<>());
78+
};
79+
80+
return ResponseEntity.ok(response);
81+
}
82+
83+
@Operation(
84+
summary = "자신의 문제풀이 히트맵 조회",
85+
description = "자신의 문제풀이 내역을 기반으로 히트맵 데이터를 반환합니다."
86+
)
87+
@LogMonitoring
88+
@GetMapping("me/heat-map")
89+
public ResponseEntity<List<HeatMapDataPoint>> getHeatMapPointsForPlayer(
90+
@AuthenticationPrincipal Player principal
91+
) {
92+
Player player = playerQueryService.findOneByUid(principal.getUid());
93+
List<HeatMapDataPoint> response = playerProfileService.getHeatMapDataPointsByModifiedDate(player);
94+
return ResponseEntity.ok(response);
95+
}
96+
97+
@Operation(
98+
summary = "관리자 권한 확인",
99+
description = "현재 로그인된 사용자가 ADMIN 또는 SUPER_ADMIN 권한을 가지고 있는지 확인합니다."
100+
)
101+
@LogMonitoring
102+
@GetMapping("me/admin")
103+
public ResponseEntity<BasicResponse> checkAdminRole(@AuthenticationPrincipal Player principal) {
104+
Player player = playerQueryService.findOneByUid(principal.getUid());
105+
boolean isAdmin = playerProfileService.isAdmin(player);
106+
return ResponseEntity.ok(BasicResponse.of(String.valueOf(isAdmin)));
107+
}
108+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.codequistify.master.application.player.dto;
2+
3+
import org.codequistify.master.core.domain.player.model.Player;
4+
5+
import java.util.List;
6+
7+
public record PlayerProfile(
8+
String uid,
9+
String name,
10+
String email,
11+
Integer level,
12+
List<String> roles
13+
) {
14+
public static PlayerProfile from(Player player) {
15+
return new PlayerProfile(
16+
player.getUid().getValue(),
17+
player.getName(),
18+
player.getEmail(),
19+
player.getExp(),
20+
player.getRoles().stream().toList()
21+
);
22+
}
23+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.codequistify.master.application.player.dto;
2+
3+
import org.codequistify.master.core.domain.stage.dto.StageCodeDTO;
4+
5+
import java.util.List;
6+
7+
public record PlayerStageProgressResponse(
8+
List<StageCodeDTO> stageCodeDTOS
9+
) {
10+
}

src/main/java/org/codequistify/master/domain/player/dto/UpdatePasswordRequest.java renamed to src/main/java/org/codequistify/master/application/player/dto/UpdatePasswordRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.codequistify.master.domain.player.dto;
1+
package org.codequistify.master.application.player.dto;
22

33
import jakarta.validation.constraints.NotBlank;
44

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.codequistify.master.application.player.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.codequistify.master.application.exception.ApplicationException;
5+
import org.codequistify.master.application.player.dto.UpdatePasswordRequest;
6+
import org.codequistify.master.core.domain.player.model.Player;
7+
import org.codequistify.master.core.domain.player.service.PlayerPasswordService;
8+
import org.codequistify.master.global.aspect.LogExecutionTime;
9+
import org.codequistify.master.global.aspect.LogMonitoring;
10+
import org.codequistify.master.global.exception.ErrorCode;
11+
import org.codequistify.master.infrastructure.player.converter.PlayerConverter;
12+
import org.codequistify.master.infrastructure.player.repository.PlayerRepository;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
import org.springframework.http.HttpStatus;
16+
import org.springframework.stereotype.Service;
17+
import org.springframework.transaction.annotation.Transactional;
18+
19+
@Service
20+
@RequiredArgsConstructor
21+
public class PlayerDetailsService {
22+
23+
private final PlayerRepository playerRepository;
24+
private final PlayerPasswordService playerPasswordService;
25+
26+
private final Logger logger = LoggerFactory.getLogger(PlayerDetailsService.class);
27+
28+
@Transactional
29+
@LogExecutionTime
30+
public void updatePassword(Player player, UpdatePasswordRequest request) {
31+
Player updated = playerRepository.findByUid(player.getUid().getValue())
32+
.map(PlayerConverter::toDomain)
33+
.map(current -> {
34+
if (!playerPasswordService.matches(current, request.rawPassword())) {
35+
throw new ApplicationException(ErrorCode.INVALID_EMAIL_OR_PASSWORD, HttpStatus.BAD_REQUEST);
36+
}
37+
return playerPasswordService.encodePassword(current, request.password());
38+
})
39+
.orElseThrow(() -> new ApplicationException(ErrorCode.PLAYER_NOT_FOUND, HttpStatus.NOT_FOUND));
40+
41+
playerRepository.save(PlayerConverter.toEntity(updated));
42+
logger.info("[updatePassword] Player: {}, 비밀번호 변경 성공", updated.getUid());
43+
}
44+
45+
@Transactional
46+
@LogMonitoring
47+
public void resetPassword(Player player, UpdatePasswordRequest request) {
48+
Player updated = playerPasswordService.encodePassword(player, request.password());
49+
playerRepository.save(PlayerConverter.toEntity(updated));
50+
51+
logger.info("[resetPassword] Player: {}, 비밀번호 초기화 성공", updated.getUid());
52+
}
53+
54+
@Transactional
55+
public void deletePlayer(Player player) {
56+
Player deleted = player.dataClear();
57+
playerRepository.save(PlayerConverter.toEntity(deleted));
58+
59+
logger.info("[deletePlayer] Player: {}, 계정 삭제 처리 완료", deleted.getUid());
60+
}
61+
}

0 commit comments

Comments
 (0)