Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cba585f
:bug: fix : use EggType in Runimo
jeeheaG Apr 3, 2025
01d84b7
:sparkles: add : make RunimoType interface
jeeheaG Apr 3, 2025
11a43b5
:sparkles: add : add img url column in Runimo
jeeheaG Apr 4, 2025
fc9325a
:bug: fix : change UserRunimo constructor private
jeeheaG Apr 4, 2025
6c35bb8
:sparkles: add : make Runimo and UserRunimo repositories
jeeheaG Apr 4, 2025
81c18eb
:sparkles: add : make findByEggId method in IncubatingEggRepository
jeeheaG Apr 4, 2025
1fc59b9
:sparkles: add : make exception and response code class for hatch
jeeheaG Apr 4, 2025
f74bed4
:sparkles: add : make usecase for hatch
jeeheaG Apr 4, 2025
182ab23
:sparkles: add : make HatchController and response class for hatch api
jeeheaG Apr 4, 2025
efb4755
:bug: fix : fix BusinessException handling
jeeheaG Apr 4, 2025
19290ea
:bug: fix : fix hatch response code
jeeheaG Apr 4, 2025
4d58ad7
:memo: docs : add swagger for hatch api
jeeheaG Apr 4, 2025
43bea2b
:bug: fix : add transaction in HatchUsecaseImpl
jeeheaG Apr 4, 2025
f944174
:sparkles: add : add main runimo id column in users table
jeeheaG Apr 4, 2025
ff627d7
:bug: fix : fix response http status code error
jeeheaG Apr 4, 2025
8f2df72
:sparkles: add : add dummy url
jeeheaG Apr 4, 2025
2fed68d
:sparkles: add : make findByRunimoId and findAllByUserId methods in U…
jeeheaG Apr 4, 2025
2fdc852
:sparkles: add : make exception and response code class in Runimo
jeeheaG Apr 4, 2025
8e65d9c
:sparkles: add : make usecase in runimo
jeeheaG Apr 4, 2025
60180ba
:sparkles: feat : make getMyRunimoList and setMainRunimo api in runimo
jeeheaG Apr 4, 2025
241af52
:white_check_mark: test : make test data sql
jeeheaG Apr 4, 2025
551cda1
:white_check_mark: test : make tests for hatch and runimo apis
jeeheaG Apr 4, 2025
32e5f57
:white_check_mark: docs : add gitignore .env
jeeheaG Apr 4, 2025
9d20896
:bug: fix : resolve conflict
jeeheaG Apr 4, 2025
cb565a6
:bug: fix : fix validateOwner logic in setMainRunimo api
jeeheaG Apr 5, 2025
87ed90b
:recycle: refactor : rename method validIncubatingEgg to validAndHatc…
jeeheaG Apr 5, 2025
1f8a627
:recycle: refactor : add exception handler of custom exceptions
jeeheaG Apr 5, 2025
bda93fe
:bug: fix : change hatch api to use incubatingEggId instead of eggId
jeeheaG Apr 5, 2025
0e513be
:bug: fix : add of() method in custom exception classes
jeeheaG Apr 5, 2025
a179d7e
:bug: fix : fix typo of sql
jeeheaG Apr 5, 2025
82e6ac9
:white_check_mark: test : add fail test case for hatch api
jeeheaG Apr 5, 2025
3892918
:bug: fix : remove useless logic in setMainRunimo validateOwner()
jeeheaG Apr 5, 2025
f82daac
:memo: docs : remove useless response code docs
jeeheaG Apr 5, 2025
8190d68
:white_check_mark: test : make fail test cases for setMainRunimo
jeeheaG Apr 5, 2025
c08bc76
:memo: docs : add swagger doc for hatch, runimo dtos
jeeheaG Apr 6, 2025
e17a0e0
:recycle: refactor : use exist query instead of find query method at …
jeeheaG Apr 6, 2025
1dec852
:sparkles: add : add duplicate runimo logic in hatch api
jeeheaG Apr 6, 2025
c34c509
:white_check_mark: test : modify hatch test
jeeheaG Apr 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env*

HELP.md
.gradle
build/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@ public static BusinessException of(CustomResponseCode errorCode) {
public String getName() {
return this.getClass().getSimpleName();
}

@Override
public String getMessage() {
return errorCode.getClientMessage();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.persistence.LockTimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.runimo.runimo.common.response.ErrorResponse;
import org.runimo.runimo.hatch.exception.HatchException;
import org.runimo.runimo.runimo.exception.RunimoException;
import org.runimo.runimo.user.exceptions.SignUpException;
import org.runimo.runimo.user.exceptions.UserJwtException;
import org.springframework.http.HttpStatus;
Expand All @@ -22,6 +24,19 @@ public class GlobalExceptionHandler {

private static final String ERROR_LOG_HEADER = "ERROR: ";


@ExceptionHandler(RunimoException.class)
public ResponseEntity<ErrorResponse> handleRunimoException(RunimoException e) {
log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
return ResponseEntity.status(e.getHttpStatusCode()).body(ErrorResponse.of(e.getErrorCode()));
}

@ExceptionHandler(HatchException.class)
public ResponseEntity<ErrorResponse> handleHatchException(HatchException e) {
log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
return ResponseEntity.status(e.getHttpStatusCode()).body(ErrorResponse.of(e.getErrorCode()));
}

@ExceptionHandler(UserJwtException.class)
public ResponseEntity<ErrorResponse> handleUserJwtException(UserJwtException e) {
log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.runimo.runimo.hatch.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.common.response.SuccessResponse;
import org.runimo.runimo.hatch.controller.dto.response.HatchEggResponse;
import org.runimo.runimo.hatch.exception.HatchHttpResponseCode;
import org.runimo.runimo.hatch.service.usecase.HatchUsecase;
import org.runimo.runimo.user.controller.UserId;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class HatchController {
private final HatchUsecase hatchUsecase;

@Operation(summary = "알 부화", description = "알을 부화합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "[HSH2011] 알 부화 성공"),
@ApiResponse(responseCode = "400", description = "[HEH4001] 부화 요청 알이 부화 가능한 상태가 아님"),
@ApiResponse(responseCode = "404", description = "[HEH4041] 부화 요청 알이 존재하지 않음")
})
@PostMapping("/api/v1/incubating-eggs/{incubatingEggId}/hatch")
public ResponseEntity<SuccessResponse<HatchEggResponse>> hatch(
@UserId Long userId,
@PathVariable Long incubatingEggId){
HatchEggResponse response = hatchUsecase.execute(userId, incubatingEggId);

return ResponseEntity.status(HttpStatus.CREATED).body(
SuccessResponse.of(
HatchHttpResponseCode.HATCH_EGG_SUCCESS,
response)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.runimo.runimo.hatch.controller.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "알 부화 요청")
public record HatchEggResponse(
@Schema(description = "부화 결과 생성된 러니모 이름", example = "토끼")
String name,

@Schema(description = "부화 결과 생성된 러니모 이미지 url", example = "http://...")
String imgUrl,

@Schema(description = "부화 결과 생성된 러니모 code", example = "R-101")
String code,

@Schema(description = "부화 결과 생성된 러니모를 이미 보유중인지", example = "true")
Boolean isDuplicated
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.runimo.runimo.hatch.exception;

import org.runimo.runimo.exceptions.BusinessException;
import org.runimo.runimo.exceptions.code.CustomResponseCode;

public class HatchException extends BusinessException {
Comment thread
ekgns33 marked this conversation as resolved.
protected HatchException(CustomResponseCode errorCode) {
super(errorCode);
}

protected HatchException(CustomResponseCode errorCode, String logMessage) {
super(errorCode, logMessage);
}

public static HatchException of(CustomResponseCode errorCode) {
return new HatchException(errorCode, errorCode.getLogMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.runimo.runimo.hatch.exception;

import org.runimo.runimo.exceptions.code.CustomResponseCode;
import org.springframework.http.HttpStatus;

public enum HatchHttpResponseCode implements CustomResponseCode {
HATCH_EGG_SUCCESS("HSH2011","알 부화 성공" , "알 부화 성공", HttpStatus.CREATED),

HATCH_EGG_NOT_READY("HEH4001", "부화 요청 알이 부화 가능한 상태가 아님", "부화 요청 알이 부화 가능한 상태가 아님", HttpStatus.BAD_REQUEST),
HATCH_EGG_NOT_FOUND("HEH4041", "부화 요청 알이 존재하지 않음", "부화 요청 알이 존재하지 않음", HttpStatus.NOT_FOUND),
;


private final String code;
private final String clientMessage;
private final String logMessage;
private final HttpStatus httpStatus;

HatchHttpResponseCode(String code, String clientMessage, String logMessage, HttpStatus httpStatus) {
this.code = code;
this.clientMessage = clientMessage;
this.logMessage = logMessage;
this.httpStatus = httpStatus;
}

@Override
public String getCode() {
return this.code;
}

@Override
public String getClientMessage() {
return this.clientMessage;
}

@Override
public String getLogMessage() {
return this.logMessage;
}

@Override
public HttpStatus getHttpStatusCode() {
return httpStatus;
}
}

15 changes: 15 additions & 0 deletions src/main/java/org/runimo/runimo/hatch/service/HatchClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.runimo.runimo.hatch.service;

import org.runimo.runimo.item.domain.EggType;
import org.runimo.runimo.runimo.domain.Runimo;
import org.runimo.runimo.user.domain.IncubatingEgg;
import org.springframework.stereotype.Component;

@Component
public class HatchClient {

// TODO : 로직 구현
public Runimo getRunimoFromEgg(IncubatingEgg incubatingEgg) {
return new Runimo("토끼_dummy", "R-100", "마당에 사는 토끼 dummy", "http://dummy", EggType.MADANG);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.runimo.runimo.hatch.service.usecase;

import org.runimo.runimo.hatch.controller.dto.response.HatchEggResponse;

public interface HatchUsecase {
HatchEggResponse execute(Long userId, Long eggId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.runimo.runimo.hatch.service.usecase;

import lombok.RequiredArgsConstructor;
import org.runimo.runimo.hatch.controller.dto.response.HatchEggResponse;
import org.runimo.runimo.hatch.exception.HatchException;
import org.runimo.runimo.hatch.exception.HatchHttpResponseCode;
import org.runimo.runimo.hatch.service.HatchClient;
import org.runimo.runimo.runimo.domain.Runimo;
import org.runimo.runimo.runimo.domain.UserRunimo;
import org.runimo.runimo.runimo.repository.RunimoRepository;
import org.runimo.runimo.runimo.repository.UserRunimoRepository;
import org.runimo.runimo.user.domain.IncubatingEgg;
import org.runimo.runimo.user.repository.IncubatingEggRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class HatchUsecaseImpl implements HatchUsecase {
private final RunimoRepository runimoRepository;
private final IncubatingEggRepository incubatingEggRepository;
private final UserRunimoRepository userRunimoRepository;
private final HatchClient hatchClient;

@Transactional
@Override
public HatchEggResponse execute(Long userId, Long incubatingEggId) {
IncubatingEgg incubatingEgg = incubatingEggRepository.findById(incubatingEggId)
.orElseThrow(() -> HatchException.of(HatchHttpResponseCode.HATCH_EGG_NOT_FOUND));
validAndHatchIncubatingEgg(incubatingEgg);

// 부화 - 러니모 획득
Runimo runimo = hatchClient.getRunimoFromEgg(incubatingEgg);

// 이미 보유한 러니모인지 확인
boolean isDuplicatedRunimo = userRunimoRepository.existsByUserIdAndRunimoId(userId, runimo.getId());

// 러니모 저장 -> 나중에 다른 요청으로 빼야 할 듯
Runimo runimoSaved = runimoRepository.save(runimo);
UserRunimo userRunimo = UserRunimo.builder()
.userId(userId)
.runimoId(runimoSaved.getId())
.build();
userRunimoRepository.save(userRunimo);

return new HatchEggResponse(
runimo.getName(),
runimo.getImgUrl(),
runimo.getCode(),
isDuplicatedRunimo
);
}

void validAndHatchIncubatingEgg(IncubatingEgg incubatingEgg){
if(incubatingEgg.isReadyToHatch()){
incubatingEgg.hatch();
} else {
throw HatchException.of(HatchHttpResponseCode.HATCH_EGG_NOT_READY);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.runimo.runimo.runimo.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.common.response.SuccessResponse;
import org.runimo.runimo.runimo.controller.dto.response.GetMyRunimoListResponse;
import org.runimo.runimo.runimo.controller.dto.response.SetMainRunimoResponse;
import org.runimo.runimo.runimo.exception.RunimoHttpResponseCode;
import org.runimo.runimo.runimo.service.usecase.RunimoUsecase;
import org.runimo.runimo.user.controller.UserId;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class RunimoController {
private final RunimoUsecase runimoUsecase;

@Operation(summary = "보유 러니모 조회", description = "사용자가 보유한 러니모 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "[MSH2001] 나의 보유 러니모 목록 조회 성공")
})
@GetMapping("/api/v1/runimos/my")
public ResponseEntity<SuccessResponse<GetMyRunimoListResponse>> getMyRunimoList(@UserId Long userId){
GetMyRunimoListResponse response = runimoUsecase.getMyRunimoList(userId);

return ResponseEntity.ok().body(
SuccessResponse.of(
RunimoHttpResponseCode.GET_MY_RUNIMO_LIST_SUCCESS,
response)
);
}

@Operation(summary = "대표 러니모 설정", description = "사용자의 대표 러니모를 설정합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "[MSH2002] 대표 러니모 설정 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패"),
@ApiResponse(responseCode = "403", description = "[MSH4031] 요청 러니모의 소유자가 아님"),
@ApiResponse(responseCode = "404", description = "[MSH4041] 요청 러니모가 존재하지 않음")
})
@PatchMapping("/api/v1/runimos/{runimoId}/main")
public ResponseEntity<SuccessResponse<SetMainRunimoResponse>> setMainRunimo(
@UserId Long userId,
@PathVariable Long runimoId){
SetMainRunimoResponse response = runimoUsecase.setMainRunimo(userId, runimoId);

return ResponseEntity.ok().body(
SuccessResponse.of(
RunimoHttpResponseCode.SET_MAIN_RUNIMO_SUCCESS,
response)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.runimo.runimo.runimo.controller.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

import java.util.List;

@Schema(description = "보유 러니모 목록 조회 응답")
public record GetMyRunimoListResponse(
List<RunimoInfo> myRunimos
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.runimo.runimo.runimo.controller.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record RunimoInfo(
@Schema(description = "러니모 id", example = "0")
Long id,

@Schema(description = "러니모 이름", example = "토끼")
String name,

@Schema(description = "러니모 이미지 url", example = "http://...")
String imgUrl,

@Schema(description = "러니모 code", example = "R-101")
String code,

@Schema(description = "러니모가 태어난 알 속성", example = "마당")
String eggType,

@Schema(description = "러니모 상세설명", example = "마당알에서 태어난 마당 토끼예요.")
String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.runimo.runimo.runimo.controller.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "대표 러니모 등록 응답")
public record SetMainRunimoResponse(
@Schema(description = "대표 러니모로 설정된 러니모 id", example = "0")
Long mainRunimoId
) {
}
20 changes: 20 additions & 0 deletions src/main/java/org/runimo/runimo/runimo/domain/MadangRunimo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.runimo.runimo.runimo.domain;

import lombok.Getter;

@Getter
public enum MadangRunimo implements RunimoType {
RABBIT("R-101", "토끼"),
DOG("R-102", "강아지"),
DUCK("R-103", "오리"),
WOLF("R-104", "늑대"),
;

private final String code;
private final String nickname;

MadangRunimo(String code, String nickname) {
this.code = code;
this.nickname = nickname;
}
}
Loading
Loading