diff --git a/.gitignore b/.gitignore index bd3712a5..b130afa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.env* + HELP.md .gradle build/ diff --git a/src/main/java/org/runimo/runimo/exceptions/BusinessException.java b/src/main/java/org/runimo/runimo/exceptions/BusinessException.java index 144a02d9..040a5e77 100644 --- a/src/main/java/org/runimo/runimo/exceptions/BusinessException.java +++ b/src/main/java/org/runimo/runimo/exceptions/BusinessException.java @@ -32,5 +32,10 @@ public static BusinessException of(CustomResponseCode errorCode) { public String getName() { return this.getClass().getSimpleName(); } + + @Override + public String getMessage() { + return errorCode.getClientMessage(); + } } diff --git a/src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java b/src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java index 931f60c8..c01ff033 100644 --- a/src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java @@ -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; @@ -22,6 +24,19 @@ public class GlobalExceptionHandler { private static final String ERROR_LOG_HEADER = "ERROR: "; + + @ExceptionHandler(RunimoException.class) + public ResponseEntity 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 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 handleUserJwtException(UserJwtException e) { log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e); diff --git a/src/main/java/org/runimo/runimo/hatch/controller/HatchController.java b/src/main/java/org/runimo/runimo/hatch/controller/HatchController.java new file mode 100644 index 00000000..c29e2668 --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/controller/HatchController.java @@ -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> 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) + ); + } + +} diff --git a/src/main/java/org/runimo/runimo/hatch/controller/dto/response/HatchEggResponse.java b/src/main/java/org/runimo/runimo/hatch/controller/dto/response/HatchEggResponse.java new file mode 100644 index 00000000..21be66fe --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/controller/dto/response/HatchEggResponse.java @@ -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 +) { +} diff --git a/src/main/java/org/runimo/runimo/hatch/exception/HatchException.java b/src/main/java/org/runimo/runimo/hatch/exception/HatchException.java new file mode 100644 index 00000000..ff29d34f --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/exception/HatchException.java @@ -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 { + 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()); + } +} diff --git a/src/main/java/org/runimo/runimo/hatch/exception/HatchHttpResponseCode.java b/src/main/java/org/runimo/runimo/hatch/exception/HatchHttpResponseCode.java new file mode 100644 index 00000000..617a9582 --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/exception/HatchHttpResponseCode.java @@ -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; + } +} + diff --git a/src/main/java/org/runimo/runimo/hatch/service/HatchClient.java b/src/main/java/org/runimo/runimo/hatch/service/HatchClient.java new file mode 100644 index 00000000..894be6be --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/service/HatchClient.java @@ -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); + } +} diff --git a/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecase.java b/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecase.java new file mode 100644 index 00000000..46adc1d9 --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecase.java @@ -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); +} diff --git a/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecaseImpl.java b/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecaseImpl.java new file mode 100644 index 00000000..eb4b984b --- /dev/null +++ b/src/main/java/org/runimo/runimo/hatch/service/usecase/HatchUsecaseImpl.java @@ -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); + } + } +} diff --git a/src/main/java/org/runimo/runimo/runimo/controller/RunimoController.java b/src/main/java/org/runimo/runimo/runimo/controller/RunimoController.java new file mode 100644 index 00000000..3a1f8872 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/controller/RunimoController.java @@ -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> 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> 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) + ); + } + +} diff --git a/src/main/java/org/runimo/runimo/runimo/controller/dto/response/GetMyRunimoListResponse.java b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/GetMyRunimoListResponse.java new file mode 100644 index 00000000..c7f448d2 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/GetMyRunimoListResponse.java @@ -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 myRunimos +) { +} diff --git a/src/main/java/org/runimo/runimo/runimo/controller/dto/response/RunimoInfo.java b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/RunimoInfo.java new file mode 100644 index 00000000..d72231b3 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/RunimoInfo.java @@ -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 +) { +} diff --git a/src/main/java/org/runimo/runimo/runimo/controller/dto/response/SetMainRunimoResponse.java b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/SetMainRunimoResponse.java new file mode 100644 index 00000000..f8b0e61f --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/controller/dto/response/SetMainRunimoResponse.java @@ -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 +) { +} diff --git a/src/main/java/org/runimo/runimo/runimo/domain/MadangRunimo.java b/src/main/java/org/runimo/runimo/runimo/domain/MadangRunimo.java new file mode 100644 index 00000000..e10a667e --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/domain/MadangRunimo.java @@ -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; + } +} diff --git a/src/main/java/org/runimo/runimo/runimo/domain/Runimo.java b/src/main/java/org/runimo/runimo/runimo/domain/Runimo.java index 89920f4e..af474d56 100644 --- a/src/main/java/org/runimo/runimo/runimo/domain/Runimo.java +++ b/src/main/java/org/runimo/runimo/runimo/domain/Runimo.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.runimo.runimo.common.BaseEntity; +import org.runimo.runimo.item.domain.EggType; @Entity @Table(name = "runimo") @@ -15,20 +16,25 @@ public class Runimo extends BaseEntity { @Column(name = "name") private String name; - @Column(name = "code") + @Column(name = "code", nullable = false) private String code; @Column(name = "description") private String description; + @Column(name = "img_url") + private String imgUrl; + @Enumerated(EnumType.STRING) - @Column(name = "type", nullable = false) - private RunimoType type; + @Column(name = "egg_type", nullable = false) + private EggType type; @Builder - public Runimo(String name, String description, RunimoType type) { + public Runimo(String name, String code, String description, String imgUrl, EggType type) { this.name = name; + this.code = code; this.description = description; + this.imgUrl = imgUrl; this.type = type; } } diff --git a/src/main/java/org/runimo/runimo/runimo/domain/RunimoType.java b/src/main/java/org/runimo/runimo/runimo/domain/RunimoType.java index 47777520..6fb508b0 100644 --- a/src/main/java/org/runimo/runimo/runimo/domain/RunimoType.java +++ b/src/main/java/org/runimo/runimo/runimo/domain/RunimoType.java @@ -1,23 +1,7 @@ package org.runimo.runimo.runimo.domain; -import lombok.Getter; - -import java.util.Arrays; -import java.util.List; - -@Getter -public enum RunimoType { - RABBIT("R-101", "토끼"), - DOG("R-102", "강아지"), - DUCK("R-103", "오리"), - WOLF("R-104", "늑대"), - ; - - private final String code; - private final String name; - - RunimoType(String code, String name) { - this.code = code; - this.name = name; - } +public interface RunimoType { + String name(); + String getCode(); + String getNickname(); } diff --git a/src/main/java/org/runimo/runimo/runimo/domain/UserRunimo.java b/src/main/java/org/runimo/runimo/runimo/domain/UserRunimo.java index cfd147e8..62fcf978 100644 --- a/src/main/java/org/runimo/runimo/runimo/domain/UserRunimo.java +++ b/src/main/java/org/runimo/runimo/runimo/domain/UserRunimo.java @@ -2,7 +2,6 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Builder; @@ -23,7 +22,7 @@ public class UserRunimo extends BaseEntity { private Long runimoId; @Builder - public UserRunimo(Long id, Long userId, Long runimoId) { + private UserRunimo(Long id, Long userId, Long runimoId) { this.id = id; this.userId = userId; this.runimoId = runimoId; diff --git a/src/main/java/org/runimo/runimo/runimo/exception/RunimoException.java b/src/main/java/org/runimo/runimo/runimo/exception/RunimoException.java new file mode 100644 index 00000000..bc86f2a8 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/exception/RunimoException.java @@ -0,0 +1,18 @@ +package org.runimo.runimo.runimo.exception; + +import org.runimo.runimo.exceptions.BusinessException; +import org.runimo.runimo.exceptions.code.CustomResponseCode; + +public class RunimoException extends BusinessException { + protected RunimoException(CustomResponseCode errorCode) { + super(errorCode); + } + + protected RunimoException(CustomResponseCode errorCode, String logMessage) { + super(errorCode, logMessage); + } + + public static RunimoException of(CustomResponseCode errorCode) { + return new RunimoException(errorCode, errorCode.getLogMessage()); + } +} diff --git a/src/main/java/org/runimo/runimo/runimo/exception/RunimoHttpResponseCode.java b/src/main/java/org/runimo/runimo/runimo/exception/RunimoHttpResponseCode.java new file mode 100644 index 00000000..250136ed --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/exception/RunimoHttpResponseCode.java @@ -0,0 +1,45 @@ +package org.runimo.runimo.runimo.exception; + +import org.runimo.runimo.exceptions.code.CustomResponseCode; +import org.springframework.http.HttpStatus; + +public enum RunimoHttpResponseCode implements CustomResponseCode { + GET_MY_RUNIMO_LIST_SUCCESS("MSH2001","보유 러니모 목록 조회 성공" , "보유 러니모 목록 조회 성공", HttpStatus.OK), + SET_MAIN_RUNIMO_SUCCESS("MSH2002", "대표 러니모 설정 성공", "대표 러니모 설정 성공", HttpStatus.OK), + + USER_DO_NOT_OWN_RUNIMO("MSH4031", "요청 러니모의 소유자가 아님", "요청 러니모의 소유자가 아님", HttpStatus.FORBIDDEN), + RUNIMO_NOT_FOUND("MSH4041", "요청 러니모가 존재하지 않음", "요청 러니모가 존재하지 않음", HttpStatus.NOT_FOUND); + + private final String code; + private final String clientMessage; + private final String logMessage; + private final HttpStatus httpStatus; + + RunimoHttpResponseCode(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; + } +} + diff --git a/src/main/java/org/runimo/runimo/runimo/repository/RunimoRepository.java b/src/main/java/org/runimo/runimo/runimo/repository/RunimoRepository.java new file mode 100644 index 00000000..79c23f19 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/repository/RunimoRepository.java @@ -0,0 +1,7 @@ +package org.runimo.runimo.runimo.repository; + +import org.runimo.runimo.runimo.domain.Runimo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RunimoRepository extends JpaRepository { +} diff --git a/src/main/java/org/runimo/runimo/runimo/repository/UserRunimoRepository.java b/src/main/java/org/runimo/runimo/runimo/repository/UserRunimoRepository.java new file mode 100644 index 00000000..6544fd16 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/repository/UserRunimoRepository.java @@ -0,0 +1,23 @@ +package org.runimo.runimo.runimo.repository; + +import org.runimo.runimo.runimo.domain.UserRunimo; +import org.runimo.runimo.runimo.service.model.RunimoSimpleModel; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface UserRunimoRepository extends JpaRepository { + + boolean existsByUserIdAndRunimoId(Long UserId, Long runimoId); + + @Query(""" + select new org.runimo.runimo.runimo.service.model.RunimoSimpleModel(r.id, r.name, r.imgUrl, r.code, r.type, r.description) + from UserRunimo ur + join Runimo r on r.id = ur.runimoId + where ur.userId = :userId + """) + List findAllByUserId(@Param("userId") Long userId); +} diff --git a/src/main/java/org/runimo/runimo/runimo/service/model/RunimoSimpleModel.java b/src/main/java/org/runimo/runimo/runimo/service/model/RunimoSimpleModel.java new file mode 100644 index 00000000..145e4e5e --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/service/model/RunimoSimpleModel.java @@ -0,0 +1,37 @@ +package org.runimo.runimo.runimo.service.model; + +import org.runimo.runimo.item.domain.EggType; +import org.runimo.runimo.runimo.controller.dto.response.RunimoInfo; + +import java.util.List; + +public record RunimoSimpleModel( + Long id, + String name, + String imgUrl, + String code, + String eggType, + String description +) { + + public RunimoSimpleModel(Long id, String name, String imgUrl, String code, String eggType, String description) { + this.id = id; + this.name = name; + this.imgUrl = imgUrl; + this.code = code; + this.eggType = eggType; + this.description = description; + } + + public RunimoSimpleModel(Long id, String name, String imgUrl, String code, EggType eggType, String description) { + this(id, name, imgUrl, code, eggType.name(), description); + } + + private RunimoInfo toDto(){ + return new RunimoInfo(id, name, imgUrl, code, eggType, description); + } + + public static List toDtoList(List modelList){ + return modelList.stream().map(RunimoSimpleModel::toDto).toList(); + } +} diff --git a/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecase.java b/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecase.java new file mode 100644 index 00000000..e79f39cc --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecase.java @@ -0,0 +1,10 @@ +package org.runimo.runimo.runimo.service.usecase; + +import org.runimo.runimo.runimo.controller.dto.response.GetMyRunimoListResponse; +import org.runimo.runimo.runimo.controller.dto.response.SetMainRunimoResponse; + +public interface RunimoUsecase { + GetMyRunimoListResponse getMyRunimoList(Long userId); + + SetMainRunimoResponse setMainRunimo(Long userId, Long runimoId); +} diff --git a/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecaseImpl.java b/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecaseImpl.java new file mode 100644 index 00000000..8c800766 --- /dev/null +++ b/src/main/java/org/runimo/runimo/runimo/service/usecase/RunimoUsecaseImpl.java @@ -0,0 +1,52 @@ +package org.runimo.runimo.runimo.service.usecase; + +import lombok.RequiredArgsConstructor; +import org.runimo.runimo.runimo.controller.dto.response.GetMyRunimoListResponse; +import org.runimo.runimo.runimo.controller.dto.response.SetMainRunimoResponse; +import org.runimo.runimo.runimo.domain.Runimo; +import org.runimo.runimo.runimo.exception.RunimoException; +import org.runimo.runimo.runimo.exception.RunimoHttpResponseCode; +import org.runimo.runimo.runimo.repository.RunimoRepository; +import org.runimo.runimo.runimo.repository.UserRunimoRepository; +import org.runimo.runimo.runimo.service.model.RunimoSimpleModel; +import org.runimo.runimo.user.domain.User; +import org.runimo.runimo.user.service.UserFinder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.NoSuchElementException; + +@Transactional(readOnly = true) +@RequiredArgsConstructor +@Service +public class RunimoUsecaseImpl implements RunimoUsecase{ + private final RunimoRepository runimoRepository; + private final UserRunimoRepository userRunimoRepository; + private final UserFinder userFinder; + + public GetMyRunimoListResponse getMyRunimoList(Long userId) { + List runimos = userRunimoRepository.findAllByUserId(userId); + return new GetMyRunimoListResponse(RunimoSimpleModel.toDtoList(runimos)); + } + + @Transactional + public SetMainRunimoResponse setMainRunimo(Long userId, Long runimoId) { + Runimo runimo = runimoRepository.findById(runimoId) + .orElseThrow(() -> RunimoException.of(RunimoHttpResponseCode.RUNIMO_NOT_FOUND)); + validateOwner(userId, runimo.getId()); + + User user = userFinder.findUserById(userId) + .orElseThrow(NoSuchElementException::new); + + user.updateMainRunimo(runimoId); + + return new SetMainRunimoResponse(runimoId); + } + + private void validateOwner(Long userId, Long runimoId) { + if(!userRunimoRepository.existsByUserIdAndRunimoId(userId, runimoId)){ + throw RunimoException.of(RunimoHttpResponseCode.USER_DO_NOT_OWN_RUNIMO); + } + } +} diff --git a/src/main/java/org/runimo/runimo/user/domain/User.java b/src/main/java/org/runimo/runimo/user/domain/User.java index 4efa620b..2ba61368 100644 --- a/src/main/java/org/runimo/runimo/user/domain/User.java +++ b/src/main/java/org/runimo/runimo/user/domain/User.java @@ -27,6 +27,8 @@ public class User extends BaseEntity { private Long totalDistanceInMeters = 0L; @Column(name = "total_time_in_seconds", nullable = false) private Long totalTimeInSeconds = 0L; + @Column(name = "main_runimo_id") + private Long mainRunimoId; @Builder public User(String nickname, String imgUrl, Long totalDistanceInMeters, Long totalTimeInSeconds) { @@ -49,4 +51,8 @@ public void updateRunningStats(Long distance, Long time) { public void prePersist() { this.publicId = UUID.randomUUID().toString(); } + + public void updateMainRunimo(Long runimoId) { + this.mainRunimoId = runimoId; + } } \ No newline at end of file diff --git a/src/main/java/org/runimo/runimo/user/repository/IncubatingEggRepository.java b/src/main/java/org/runimo/runimo/user/repository/IncubatingEggRepository.java index 714930fe..2d2c296b 100644 --- a/src/main/java/org/runimo/runimo/user/repository/IncubatingEggRepository.java +++ b/src/main/java/org/runimo/runimo/user/repository/IncubatingEggRepository.java @@ -16,6 +16,8 @@ @Repository public interface IncubatingEggRepository extends JpaRepository { + Optional findById(Long id); + @Lock(LockModeType.PESSIMISTIC_WRITE) @QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")}) @Query("select ie from IncubatingEgg ie where ie.userId = :userId and ie.id = :eggId") diff --git a/src/main/resources/sql/schema.sql b/src/main/resources/sql/schema.sql index fda95cac..98b987be 100644 --- a/src/main/resources/sql/schema.sql +++ b/src/main/resources/sql/schema.sql @@ -23,6 +23,7 @@ CREATE TABLE `users` `img_url` VARCHAR(255), `total_distance_in_meters` BIGINT NOT NULL DEFAULT 0, `total_time_in_seconds` BIGINT NOT NULL DEFAULT 0, + `main_runimo_id` BIGINT, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `deleted_at` TIMESTAMP NULL @@ -136,7 +137,8 @@ CREATE TABLE `runimo` `name` VARCHAR(255), `code` VARCHAR(255), `description` VARCHAR(255), - `type` VARCHAR(255) NOT NULL, + `img_url` varchar(255), + `egg_type` varchar(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted_at` TIMESTAMP NULL diff --git a/src/test/java/org/runimo/runimo/CleanUpUtil.java b/src/test/java/org/runimo/runimo/CleanUpUtil.java index 00f411fc..cfabc546 100644 --- a/src/test/java/org/runimo/runimo/CleanUpUtil.java +++ b/src/test/java/org/runimo/runimo/CleanUpUtil.java @@ -13,7 +13,9 @@ public class CleanUpUtil { "users", "running_record", "user_love_point", - "incubating_egg" + "incubating_egg", + "runimo", + "user_runimo", }; @Autowired diff --git a/src/test/java/org/runimo/runimo/hatch/controller/HatchControllerTest.java b/src/test/java/org/runimo/runimo/hatch/controller/HatchControllerTest.java new file mode 100644 index 00000000..b12bdbcf --- /dev/null +++ b/src/test/java/org/runimo/runimo/hatch/controller/HatchControllerTest.java @@ -0,0 +1,114 @@ +package org.runimo.runimo.hatch.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.runimo.runimo.CleanUpUtil; +import org.runimo.runimo.auth.jwt.JwtTokenFactory; +import org.runimo.runimo.exceptions.code.CustomResponseCode; +import org.runimo.runimo.hatch.exception.HatchHttpResponseCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +class HatchControllerTest { + + @LocalServerPort + int port; + + @Autowired + private JwtTokenFactory jwtTokenFactory; + + @Autowired + private CleanUpUtil cleanUpUtil; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @AfterEach + void tearDown() { + cleanUpUtil.cleanUpUserInfos(); + } + + @Test + @Sql(scripts = "/sql/hatch_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 사용자의_알_부화_성공() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .post("/api/v1/incubating-eggs/"+"1"+"/hatch") + + .then() + .log().all() + .statusCode(HttpStatus.CREATED.value()) + + .body("code", equalTo("HSH2011")) + .body("payload.name", equalTo("토끼_dummy")) + .body("payload.img_url", equalTo("http://dummy")) + .body("payload.code", equalTo("R-100")) + .body("payload.is_duplicated", equalTo(false)); + } + + @Test + @Sql(scripts = "/sql/hatch_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 사용자의_알_부화_실패_부화가능한_상태가_아님() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + CustomResponseCode responseCode = HatchHttpResponseCode.HATCH_EGG_NOT_READY; + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .post("/api/v1/incubating-eggs/"+"2"+"/hatch") + + .then() + .log().all() + .statusCode(responseCode.getHttpStatusCode().value()) + + .body("code", equalTo(responseCode.getCode())) + .body("message", equalTo(responseCode.getClientMessage())); + } + + @Test + @Sql(scripts = "/sql/hatch_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 사용자의_알_부화_실패_알_존재하지_않음() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + CustomResponseCode responseCode = HatchHttpResponseCode.HATCH_EGG_NOT_FOUND; + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .post("/api/v1/incubating-eggs/"+"9999"+"/hatch") + + .then() + .log().all() + .statusCode(responseCode.getHttpStatusCode().value()) + + .body("code", equalTo(responseCode.getCode())) + .body("message", equalTo(responseCode.getClientMessage())); + } +} \ No newline at end of file diff --git a/src/test/java/org/runimo/runimo/runimo/controller/RunimoControllerTest.java b/src/test/java/org/runimo/runimo/runimo/controller/RunimoControllerTest.java new file mode 100644 index 00000000..846f8ff4 --- /dev/null +++ b/src/test/java/org/runimo/runimo/runimo/controller/RunimoControllerTest.java @@ -0,0 +1,138 @@ +package org.runimo.runimo.runimo.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.runimo.runimo.CleanUpUtil; +import org.runimo.runimo.auth.jwt.JwtTokenFactory; +import org.runimo.runimo.exceptions.code.CustomResponseCode; +import org.runimo.runimo.runimo.exception.RunimoHttpResponseCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles("test") +class RunimoControllerTest { + + @LocalServerPort + int port; + + @Autowired + private JwtTokenFactory jwtTokenFactory; + + @Autowired + private CleanUpUtil cleanUpUtil; + + @Autowired + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + RestAssured.port = port; + } + + @AfterEach + void tearDown() { + cleanUpUtil.cleanUpUserInfos(); + } + + + @Test + @Sql(scripts = "/sql/get_my_runimo_list_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 보유_러니모_목록_조회_성공() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .get("/api/v1/runimos/my") + + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + + .body("code", equalTo("MSH2001")) + .body("payload.my_runimos[0].id", equalTo(1)) + .body("payload.my_runimos[0].name", equalTo("토끼")) + .body("payload.my_runimos[0].img_url", equalTo("http://dummy1")) + .body("payload.my_runimos[0].code", equalTo("R-101")) + .body("payload.my_runimos[0].egg_type", equalTo("MADANG")) + .body("payload.my_runimos[0].description", equalTo("마당 토끼예여")); + } + + @Test + @Sql(scripts = "/sql/set_main_runimo_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 대표_러니모_설정_성공() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .patch("/api/v1/runimos/"+"1"+"/main") + + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + + .body("code", equalTo("MSH2002")) + .body("payload.main_runimo_id", equalTo(1)); + } + + @Test + @Sql(scripts = "/sql/set_main_runimo_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 대표_러니모_설정_실패_러니모의_소유자_아님() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + CustomResponseCode responseCode = RunimoHttpResponseCode.USER_DO_NOT_OWN_RUNIMO; + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .patch("/api/v1/runimos/"+"4"+"/main") + + .then() + .log().all() + .statusCode(responseCode.getHttpStatusCode().value()) + + .body("code", equalTo(responseCode.getCode())) + .body("message", equalTo(responseCode.getClientMessage())); + } + + @Test + @Sql(scripts = "/sql/set_main_runimo_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 대표_러니모_설정_실패_러니모_존재하지않음() { + String token = "Bearer " + jwtTokenFactory.generateAccessToken("test-user-uuid-1"); + CustomResponseCode responseCode = RunimoHttpResponseCode.RUNIMO_NOT_FOUND; + + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + + .when() + .patch("/api/v1/runimos/"+"9999"+"/main") + + .then() + .log().all() + .statusCode(responseCode.getHttpStatusCode().value()) + + .body("code", equalTo(responseCode.getCode())) + .body("message", equalTo(responseCode.getClientMessage())); + } +} \ No newline at end of file diff --git a/src/test/resources/sql/get_my_runimo_list_test_data.sql b/src/test/resources/sql/get_my_runimo_list_test_data.sql new file mode 100644 index 00000000..55165749 --- /dev/null +++ b/src/test/resources/sql/get_my_runimo_list_test_data.sql @@ -0,0 +1,24 @@ +-- 사용자 +SET FOREIGN_KEY_CHECKS = 0; +TRUNCATE TABLE users; +INSERT INTO users (id, public_id, nickname, img_url, total_distance_in_meters, total_time_in_seconds, created_at, + updated_at) +VALUES (1, 'test-user-uuid-1', 'Daniel', 'https://example.com/images/user1.png', 10000, 3600, NOW(), NOW()), + (2, 'test-user-uuid-2', 'Moon', 'https://example.com/images/user2.png', 5000, 1800, NOW(), NOW()); +SET FOREIGN_KEY_CHECKS = 1; + +-- 러니모 +TRUNCATE TABLE runimo; +INSERT INTO runimo (id, name, code, description, img_url, egg_type, created_at, updated_at) +VALUES (1, '토끼', 'R-101', '마당 토끼예여', 'http://dummy1', 'MADANG', NOW(), NOW()); +VALUES (2, '강아지', 'R-102', '마당 강아지예여', 'http://dummy2', 'MADANG', NOW(), NOW()); +VALUES (3, '오리', 'R-103', '마당 오리예여', 'http://dummy3', 'MADANG', NOW(), NOW()); +VALUES (4, '늑대', 'R-104', '마당 늑대예여', 'http://dummy4', 'MADANG', NOW(), NOW()); + +-- 사용자-알 맵핑 +TRUNCATE TABLE user_runimo; +INSERT INTO user_runimo (id, user_id, runimo_id, created_at, updated_at) +VALUES (1, 1, 1, NOW(), NOW()), + (2, 1, 2, NOW(), NOW()), + (3, 1, 3, NOW(), NOW()), + (4, 2, 4, NOW(), NOW()); \ No newline at end of file diff --git a/src/test/resources/sql/hatch_test_data.sql b/src/test/resources/sql/hatch_test_data.sql new file mode 100644 index 00000000..f4b1ae6c --- /dev/null +++ b/src/test/resources/sql/hatch_test_data.sql @@ -0,0 +1,14 @@ +-- 사용자 +SET FOREIGN_KEY_CHECKS = 0; +TRUNCATE TABLE users; +INSERT INTO users (id, public_id, nickname, img_url, total_distance_in_meters, total_time_in_seconds, created_at, + updated_at) +VALUES (1, 'test-user-uuid-1', 'Daniel', 'https://example.com/images/user1.png', 10000, 3600, NOW(), NOW()), + (2, 'test-user-uuid-2', 'Moon', 'https://example.com/images/user2.png', 5000, 1800, NOW(), NOW()); +SET FOREIGN_KEY_CHECKS = 1; + +-- 부화할 알 +TRUNCATE TABLE incubating_egg; +INSERT INTO incubating_egg (id, user_id, egg_id, current_love_point_amount, hatch_require_amount, egg_status, created_at, updated_at) +VALUES (1, 1, 1, 100, 100, 'INCUBATED', NOW(), NOW()), + (2, 1, 2, 50, 100, 'INCUBATING', NOW(), NOW()); \ No newline at end of file diff --git a/src/test/resources/sql/schema.sql b/src/test/resources/sql/schema.sql index fda95cac..ff7cb380 100644 --- a/src/test/resources/sql/schema.sql +++ b/src/test/resources/sql/schema.sql @@ -23,6 +23,7 @@ CREATE TABLE `users` `img_url` VARCHAR(255), `total_distance_in_meters` BIGINT NOT NULL DEFAULT 0, `total_time_in_seconds` BIGINT NOT NULL DEFAULT 0, + `main_runimo_id` BIGINT, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `deleted_at` TIMESTAMP NULL @@ -61,6 +62,7 @@ CREATE TABLE `oauth_account` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ); + CREATE TABLE `running_record` ( `id` INTEGER PRIMARY KEY AUTO_INCREMENT, @@ -136,7 +138,8 @@ CREATE TABLE `runimo` `name` VARCHAR(255), `code` VARCHAR(255), `description` VARCHAR(255), - `type` VARCHAR(255) NOT NULL, + `img_url` varchar(255), + `egg_type` varchar(255) NOT NULL, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted_at` TIMESTAMP NULL diff --git a/src/test/resources/sql/set_main_runimo_test_data.sql b/src/test/resources/sql/set_main_runimo_test_data.sql new file mode 100644 index 00000000..b1e54fa1 --- /dev/null +++ b/src/test/resources/sql/set_main_runimo_test_data.sql @@ -0,0 +1,23 @@ +-- 사용자 +SET FOREIGN_KEY_CHECKS = 0; +TRUNCATE TABLE users; +INSERT INTO users (id, public_id, nickname, img_url, total_distance_in_meters, total_time_in_seconds, created_at, + updated_at) +VALUES (1, 'test-user-uuid-1', 'Daniel', 'https://example.com/images/user1.png', 10000, 3600, NOW(), NOW()), + (2, 'test-user-uuid-2', 'Moon', 'https://example.com/images/user2.png', 5000, 1800, NOW(), NOW()); +SET FOREIGN_KEY_CHECKS = 1; + +-- 러니모 +TRUNCATE TABLE runimo; +INSERT INTO runimo (id, name, code, description, img_url, egg_type, created_at, updated_at) +VALUES (1, '토끼', 'R-101', '마당 토끼예여', 'http://dummy1', 'MADANG', NOW(), NOW()), + (2, '강아지', 'R-102', '마당 강아지예여', 'http://dummy2', 'MADANG', NOW(), NOW()), + (3, '오리', 'R-103', '마당 오리예여', 'http://dummy3', 'MADANG', NOW(), NOW()), + (4, '늑대', 'R-104', '주인없는 마당 늑대예여', 'http://dummy4', 'MADANG', NOW(), NOW()); + +-- 사용자-러니모 맵핑 +TRUNCATE TABLE user_runimo; +INSERT INTO user_runimo (id, user_id, runimo_id, created_at, updated_at) +VALUES (1, 1, 1, NOW(), NOW()), + (2, 1, 2, NOW(), NOW()), + (3, 1, 3, NOW(), NOW()); \ No newline at end of file