diff --git a/src/main/java/com/universe/uni/controller/HomeController.java b/src/main/java/com/universe/uni/controller/HomeController.java index 56b852c..b2e1e1c 100644 --- a/src/main/java/com/universe/uni/controller/HomeController.java +++ b/src/main/java/com/universe/uni/controller/HomeController.java @@ -1,5 +1,6 @@ package com.universe.uni.controller; +import com.universe.uni.controller.docs.HomeControllerContract; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,12 +15,14 @@ @RestController @RequiredArgsConstructor @RequestMapping("/api/home") -public class HomeController { +public class HomeController implements HomeControllerContract { private final HomeService homeService; + @Deprecated @GetMapping() @ResponseStatus(HttpStatus.OK) + @Override public HomeResponseDto getHome() { return homeService.getHome(); } diff --git a/src/main/java/com/universe/uni/controller/HomeControllerV1.java b/src/main/java/com/universe/uni/controller/HomeControllerV1.java new file mode 100644 index 0000000..05a8731 --- /dev/null +++ b/src/main/java/com/universe/uni/controller/HomeControllerV1.java @@ -0,0 +1,26 @@ +package com.universe.uni.controller; + +import com.universe.uni.controller.docs.HomeControllerV1Contract; +import com.universe.uni.dto.response.HomeResponseDto; +import com.universe.uni.service.HomeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/home") +public class HomeControllerV1 implements HomeControllerV1Contract { + + private final HomeService homeService; + + @GetMapping() + @Override + @ResponseStatus(HttpStatus.OK) + public HomeResponseDto getHome() { + return homeService.getHome(); + } +} diff --git a/src/main/java/com/universe/uni/controller/docs/CoupleControllerContract.java b/src/main/java/com/universe/uni/controller/docs/CoupleControllerContract.java index f9a1e30..938103b 100644 --- a/src/main/java/com/universe/uni/controller/docs/CoupleControllerContract.java +++ b/src/main/java/com/universe/uni/controller/docs/CoupleControllerContract.java @@ -81,6 +81,15 @@ CoupleDto createCoupleBy( schema = @Schema(implementation = ErrorResponse.class) ) ), + @ApiResponse( + responseCode = "409-UE10003", + description = "이미 가입된 커플입니다.(본인이 본인에게 연결)", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class) + ) + + ), @ApiResponse( responseCode = "500-UE500", description = "서버 내부 에러입니다", diff --git a/src/main/java/com/universe/uni/controller/docs/HomeControllerContract.java b/src/main/java/com/universe/uni/controller/docs/HomeControllerContract.java new file mode 100644 index 0000000..5a1f760 --- /dev/null +++ b/src/main/java/com/universe/uni/controller/docs/HomeControllerContract.java @@ -0,0 +1,37 @@ +package com.universe.uni.controller.docs; + +import com.universe.uni.dto.response.HomeResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Tag( + name = "Home", + description = "홈 화면에서 필요한 데이터 관련 API 입니다." +) +public interface HomeControllerContract { + + @Operation( + summary = "홈 화면 조회", + description = "사용자는 홈에 필요한 정보를 얻을 수 있다.", + responses = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = HomeResponseDto.class) + ) + ) + } + ) + @GetMapping() + @ResponseStatus(HttpStatus.OK) + HomeResponseDto getHome(); +} diff --git a/src/main/java/com/universe/uni/controller/docs/HomeControllerV1Contract.java b/src/main/java/com/universe/uni/controller/docs/HomeControllerV1Contract.java new file mode 100644 index 0000000..70a8b7d --- /dev/null +++ b/src/main/java/com/universe/uni/controller/docs/HomeControllerV1Contract.java @@ -0,0 +1,36 @@ +package com.universe.uni.controller.docs; + +import com.universe.uni.dto.response.HomeResponseDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Tag( + name = "Home", + description = "홈 화면에서 필요한 데이터 관련 API 입니다." +) +public interface HomeControllerV1Contract { + @Operation( + summary = "홈 화면 조회", + description = "사용자는 홈에 필요한 정보를 얻을 수 있다.", + responses = { + @ApiResponse( + responseCode = "200", + description = "성공", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = HomeResponseDto.class) + ) + ) + } + ) + @GetMapping() + @ResponseStatus(HttpStatus.OK) + HomeResponseDto getHome(); +} diff --git a/src/main/java/com/universe/uni/dto/response/HomeResponseDto.java b/src/main/java/com/universe/uni/dto/response/HomeResponseDto.java index c01281a..21815b2 100644 --- a/src/main/java/com/universe/uni/dto/response/HomeResponseDto.java +++ b/src/main/java/com/universe/uni/dto/response/HomeResponseDto.java @@ -6,12 +6,13 @@ import lombok.Builder; -@JsonPropertyOrder({"userId", "partnerId", "roundGameId", "myScore", "partnerScore", "drawCount", "dDay", "couple", +@JsonPropertyOrder({"userId", "partnerId","partnerNickname", "roundGameId", "myScore", "partnerScore", "drawCount", "dDay", "couple", "shortGame"}) @Builder public record HomeResponseDto( Long userId, Long partnerId, + String partnerNickname, Long roundGameId, int myScore, int partnerScore, diff --git a/src/main/java/com/universe/uni/exception/dto/ErrorType.java b/src/main/java/com/universe/uni/exception/dto/ErrorType.java index eeedb4f..20300c2 100644 --- a/src/main/java/com/universe/uni/exception/dto/ErrorType.java +++ b/src/main/java/com/universe/uni/exception/dto/ErrorType.java @@ -10,105 +10,108 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public enum ErrorType { - /** - * 400 BAD REQUEST - */ - INVALID_REQUEST_METHOD(HttpStatus.BAD_REQUEST, "UE1001", - "요청 방식이 잘못된 경우입니다. 요청 방식 자체가 잘못된 경우입니다."), - VALIDATION_TOKEN_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "UE1002", - "요청 시 토큰이 누락되어 토큰 값이 없는 경우입니다."), - ALREADY_GAME_CREATED(HttpStatus.BAD_REQUEST, "UE1003", - "이미 생성된 승부가 있습니다."), - USER_NOT_EXISTENT(HttpStatus.BAD_REQUEST, "UE1004", "존재하지 않는 유저의 요청"), - ALREADY_GAME_DONE(HttpStatus.BAD_REQUEST, "UE1005", "이미 종료된 라운드입니다."), - COUPLE_NOT_EXISTENT(HttpStatus.BAD_REQUEST, "UE1006", "존재하지 않는 커플 id 입니다"), - INVALID_INVITE_CODE(HttpStatus.BAD_REQUEST, "UE1007", "올바르지 않은 초대 코드입니다."), - TOKEN_VALUE_NOT_EXIST(HttpStatus.BAD_REQUEST, "UE1008", "토큰 값이 존재하지 않습니다."), - PARTNER_RESULT_NOT_ENTERED(HttpStatus.BAD_REQUEST, "UE1009", - "상대방이 아직 결과를 입력하지 않았습니다."), - - /** - * 401 Unauthorized - */ - EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "UE2001", "토큰이 만료된 경우입니다."), - UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "UE2002", - "서버에서 인증하지 않는 방식의 토큰 혹은 변조된 토큰을 사용한 경우입니다."), - EMPTY_SECURITY_CONTEXT(HttpStatus.UNAUTHORIZED, "UE2003", - "Security Context에 인증 정보가 없습니다."), - - - /** - * 404 NOT FOUND - */ - INVALID_ENDPOINT_EXCEPTION(HttpStatus.NOT_FOUND, "UE5001", - "잘못된 endpoint에 요청한 경우입니다."), - NOT_FOUND_USER(HttpStatus.NOT_FOUND, "UE5002", - "조회한 유저가 존재하지 않는 경우 입니다."), - NOT_FOUND_MISSION_CATEGORY_EXCEPTION(HttpStatus.NOT_FOUND, "UE5003", - "존재하지 않는 미션 카테고리입니다"), - NOT_FOUND_MISSION_CONTENT(HttpStatus.NOT_FOUND, "UE5004", - "해당 카테고리 미션이 존재하지 않습니다."), - NOT_FOUND_ROUND_MISSION(HttpStatus.NOT_FOUND, "UE5005", - "해당 라운드 미션이 존재하지 않습니다."), - NOT_FOUND_WISH_COUPON(HttpStatus.NOT_FOUND, "UE5006", "소원권이 존재하지 않습니다."), - NOT_FOUND_ROUND_GAME(HttpStatus.NOT_FOUND, "UE5007", "해당 라운드 게임이 존재하지 않습니다."), - NOT_FOUND_COUPLE(HttpStatus.NOT_FOUND, "UE5008", "커플이 존재하지 않습니다."), - - /** - * 406 Not Acceptable - */ - UNSUPPORTED_MEDIA_TYPE_EXCEPTION(HttpStatus.NOT_ACCEPTABLE, "UE7001", - "Accept 헤더에 유효하지 않거나 지원되지 않는 미디어 유형을 지정한 경우입니다."), - - /** - * 409 CONFLICT - */ - USER_ALREADY_EXISTS_EXCEPTION(HttpStatus.CONFLICT, "UE10001", - "이미 존재하는 유저에 대한 생성에 대한 경우입니다."), - COUPLE_ALREADY_CONNECTED(HttpStatus.CONFLICT, "UE10002", - "이미 커플이 연결된 초대코드입니다."), - - /** - * 415 Unsupported Media Type - */ - UNSUPPORTED_MEDIA_FORMAT_EXCEPTION(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "UE16001", - "지원하지 않는 미디어 포멧으로 요청한 경우 입니다."), - - /** - * 500 INTERNAL SERVER ERROR - */ - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "UE500", - "서버 내부 오류입니다."), - - /** - * 503 Service Unavailable - */ - SERVICE_UNAVAILABLE_EXCEPTION(HttpStatus.SERVICE_UNAVAILABLE, "UE503", - "서버가 작동중이지 않거나 과부하 상태입니다."); - - private final HttpStatus httpStatus; - private final String uniErrorCode; - private final String message; - - public int getHttpStatusCode() { - return httpStatus.value(); - } - - public String getUniErrorCode() { - return uniErrorCode; - } - - public String getMessage() { - return message; - } - - private boolean hasErrorType(HttpStatus httpStatus) { - return this.httpStatus.value() == httpStatus.value(); - } - - public static ErrorType findErrorTypeBy(HttpStatus httpStatus) { - return Arrays.stream(values()).filter((errorType -> errorType.hasErrorType(httpStatus))) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("UnSupported Business HttpStatus Code :" + httpStatus)); - } + /** + * 400 BAD REQUEST + */ + INVALID_REQUEST_METHOD(HttpStatus.BAD_REQUEST, "UE1001", + "요청 방식이 잘못된 경우입니다. 요청 방식 자체가 잘못된 경우입니다."), + VALIDATION_TOKEN_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "UE1002", + "요청 시 토큰이 누락되어 토큰 값이 없는 경우입니다."), + ALREADY_GAME_CREATED(HttpStatus.BAD_REQUEST, "UE1003", + "이미 생성된 승부가 있습니다."), + USER_NOT_EXISTENT(HttpStatus.BAD_REQUEST, "UE1004", "존재하지 않는 유저의 요청"), + ALREADY_GAME_DONE(HttpStatus.BAD_REQUEST, "UE1005", "이미 종료된 라운드입니다."), + COUPLE_NOT_EXISTENT(HttpStatus.BAD_REQUEST, "UE1006", "존재하지 않는 커플 id 입니다"), + INVALID_INVITE_CODE(HttpStatus.BAD_REQUEST, "UE1007", "올바르지 않은 초대 코드입니다."), + TOKEN_VALUE_NOT_EXIST(HttpStatus.BAD_REQUEST, "UE1008", "토큰 값이 존재하지 않습니다."), + PARTNER_RESULT_NOT_ENTERED(HttpStatus.BAD_REQUEST, "UE1009", + "상대방이 아직 결과를 입력하지 않았습니다."), + + /** + * 401 Unauthorized + */ + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "UE2001", "토큰이 만료된 경우입니다."), + UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "UE2002", + "서버에서 인증하지 않는 방식의 토큰 혹은 변조된 토큰을 사용한 경우입니다."), + EMPTY_SECURITY_CONTEXT(HttpStatus.UNAUTHORIZED, "UE2003", + "Security Context에 인증 정보가 없습니다."), + + + /** + * 404 NOT FOUND + */ + INVALID_ENDPOINT_EXCEPTION(HttpStatus.NOT_FOUND, "UE5001", + "잘못된 endpoint에 요청한 경우입니다."), + NOT_FOUND_USER(HttpStatus.NOT_FOUND, "UE5002", + "조회한 유저가 존재하지 않는 경우 입니다."), + NOT_FOUND_MISSION_CATEGORY_EXCEPTION(HttpStatus.NOT_FOUND, "UE5003", + "존재하지 않는 미션 카테고리입니다"), + NOT_FOUND_MISSION_CONTENT(HttpStatus.NOT_FOUND, "UE5004", + "해당 카테고리 미션이 존재하지 않습니다."), + NOT_FOUND_ROUND_MISSION(HttpStatus.NOT_FOUND, "UE5005", + "해당 라운드 미션이 존재하지 않습니다."), + NOT_FOUND_WISH_COUPON(HttpStatus.NOT_FOUND, "UE5006", "소원권이 존재하지 않습니다."), + NOT_FOUND_ROUND_GAME(HttpStatus.NOT_FOUND, "UE5007", "해당 라운드 게임이 존재하지 않습니다."), + NOT_FOUND_COUPLE(HttpStatus.NOT_FOUND, "UE5008", "커플이 존재하지 않습니다."), + + /** + * 406 Not Acceptable + */ + UNSUPPORTED_MEDIA_TYPE_EXCEPTION(HttpStatus.NOT_ACCEPTABLE, "UE7001", + "Accept 헤더에 유효하지 않거나 지원되지 않는 미디어 유형을 지정한 경우입니다."), + + /** + * 409 CONFLICT + */ + USER_ALREADY_EXISTS_EXCEPTION(HttpStatus.CONFLICT, "UE10001", + "이미 존재하는 유저에 대한 생성에 대한 경우입니다."), + COUPLE_ALREADY_CONNECTED(HttpStatus.CONFLICT, "UE10002", + "이미 커플이 연결된 초대코드입니다."), + + USER_ALREADY_HAS_COUPLE(HttpStatus.CONFLICT, "UE10003", + "이미 가입된 커플입니다."), + + /** + * 415 Unsupported Media Type + */ + UNSUPPORTED_MEDIA_FORMAT_EXCEPTION(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "UE16001", + "지원하지 않는 미디어 포멧으로 요청한 경우 입니다."), + + /** + * 500 INTERNAL SERVER ERROR + */ + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "UE500", + "서버 내부 오류입니다."), + + /** + * 503 Service Unavailable + */ + SERVICE_UNAVAILABLE_EXCEPTION(HttpStatus.SERVICE_UNAVAILABLE, "UE503", + "서버가 작동중이지 않거나 과부하 상태입니다."); + + private final HttpStatus httpStatus; + private final String uniErrorCode; + private final String message; + + public int getHttpStatusCode() { + return httpStatus.value(); + } + + public String getUniErrorCode() { + return uniErrorCode; + } + + public String getMessage() { + return message; + } + + private boolean hasErrorType(HttpStatus httpStatus) { + return this.httpStatus.value() == httpStatus.value(); + } + + public static ErrorType findErrorTypeBy(HttpStatus httpStatus) { + return Arrays.stream(values()).filter((errorType -> errorType.hasErrorType(httpStatus))) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("UnSupported Business HttpStatus Code :" + httpStatus)); + } } diff --git a/src/main/java/com/universe/uni/service/CoupleService.java b/src/main/java/com/universe/uni/service/CoupleService.java index dbf58fa..fd7db25 100644 --- a/src/main/java/com/universe/uni/service/CoupleService.java +++ b/src/main/java/com/universe/uni/service/CoupleService.java @@ -68,6 +68,7 @@ public void joinCouple(Long userId, String inviteCode) { validateCoupleConnected(couple); final User user = userRepository.findById(userId) .orElseThrow(() -> new BadRequestException(ErrorType.USER_NOT_EXISTENT)); + validateUserAlreadyConnected(user); user.connectCouple(couple); } @@ -78,6 +79,12 @@ private void validateCoupleConnected(Couple couple) { } } + private void validateUserAlreadyConnected(User user) { + if (user.getCouple() != null) { + throw new ConflictException(USER_ALREADY_HAS_COUPLE); + } + } + @Override @Transactional public CoupleDto updateCoupleStartDate(Long coupleId, String startDate) { diff --git a/src/main/java/com/universe/uni/service/HomeService.java b/src/main/java/com/universe/uni/service/HomeService.java index ccffee2..a297275 100644 --- a/src/main/java/com/universe/uni/service/HomeService.java +++ b/src/main/java/com/universe/uni/service/HomeService.java @@ -29,61 +29,62 @@ @RequiredArgsConstructor @Transactional public class HomeService { - private final GameRepository gameRepository; - private final UserGameHistoryRepository userGameHistoryRepository; - private final RoundGameRepository roundGameRepository; - private final UserRepository userRepository; - private final UserUtil userUtil; + private final GameRepository gameRepository; + private final UserGameHistoryRepository userGameHistoryRepository; + private final RoundGameRepository roundGameRepository; + private final UserRepository userRepository; + private final UserUtil userUtil; - public HomeResponseDto getHome() { - User user = userUtil.getCurrentUser(); - Couple couple = user.getCouple(); - User partner = userRepository.findByCoupleIdAndIdNot(user.getCouple().getId(), user.getId()); + public HomeResponseDto getHome() { + User user = userUtil.getCurrentUser(); + Couple couple = user.getCouple(); + User partner = userRepository.findByCoupleIdAndIdNot(user.getCouple().getId(), user.getId()); - Game game = gameRepository.findByCoupleIdAndEnable(couple.getId(), true); - RoundGame roundGame = game != null ? roundGameRepository.findByGameId(game.getId()) : null; + Game game = gameRepository.findByCoupleIdAndEnable(couple.getId(), true); + RoundGame roundGame = game != null ? roundGameRepository.findByGameId(game.getId()) : null; - List gameHistoryList = userGameHistoryRepository.findByUserId(user.getId()); + List gameHistoryList = userGameHistoryRepository.findByUserId(user.getId()); - int myScore = calculateScore(gameHistoryList, GameResult.WIN); - int partnerScore = calculateScore(gameHistoryList, GameResult.LOSE); - int drawCount = calculateScore(gameHistoryList, GameResult.DRAW); + int myScore = calculateScore(gameHistoryList, GameResult.WIN); + int partnerScore = calculateScore(gameHistoryList, GameResult.LOSE); + int drawCount = calculateScore(gameHistoryList, GameResult.DRAW); - int dDay = calculateDays(couple); + int dDay = calculateDays(couple); - ShortGameDto shortGameDto = game instanceof ShortGame ? new ShortGameDto((ShortGame)game) : null; - CoupleDto coupleDto = fromCoupleToCoupleDtoMapper(couple); + ShortGameDto shortGameDto = game instanceof ShortGame ? new ShortGameDto((ShortGame) game) : null; + CoupleDto coupleDto = fromCoupleToCoupleDtoMapper(couple); - return HomeResponseDto.builder() - .userId(user.getId()) - .partnerId(partner.getId()) - .roundGameId(roundGame != null ? roundGame.getId() : null) - .myScore(myScore) - .partnerScore(partnerScore) - .drawCount(drawCount) - .dDay(dDay) - .couple(coupleDto) - .shortGame(shortGameDto) - .build(); - } + return HomeResponseDto.builder() + .userId(user.getId()) + .partnerId(partner.getId()) + .partnerNickname(partner.getNickname()) + .roundGameId(roundGame != null ? roundGame.getId() : null) + .myScore(myScore) + .partnerScore(partnerScore) + .drawCount(drawCount) + .dDay(dDay) + .couple(coupleDto) + .shortGame(shortGameDto) + .build(); + } - private int calculateScore(List gameHistoryList, GameResult result) { - return gameHistoryList != null - ? (int)gameHistoryList.stream().filter(history -> history.getResult() == result).count() - : 0; - } + private int calculateScore(List gameHistoryList, GameResult result) { + return gameHistoryList != null + ? (int) gameHistoryList.stream().filter(history -> history.getResult() == result).count() + : 0; + } - private int calculateDays(Couple couple) { - ZonedDateTime localTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); - long dDay = ChronoUnit.DAYS.between(couple.getStartDate(), localTime.toLocalDate()); - return (int)dDay + 1; - } + private int calculateDays(Couple couple) { + ZonedDateTime localTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul")); + long dDay = ChronoUnit.DAYS.between(couple.getStartDate(), localTime.toLocalDate()); + return (int) dDay + 1; + } - private CoupleDto fromCoupleToCoupleDtoMapper(Couple couple) { - return CoupleDto.builder() - .id(couple.getId()) - .startDate(String.valueOf(couple.getStartDate())) - .heartToken(couple.getHeartToken()) - .build(); - } + private CoupleDto fromCoupleToCoupleDtoMapper(Couple couple) { + return CoupleDto.builder() + .id(couple.getId()) + .startDate(String.valueOf(couple.getStartDate())) + .heartToken(couple.getHeartToken()) + .build(); + } }