Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7214c1c
:memo: docs : add swagger controller name
jeeheaG Apr 10, 2025
237a6de
:sparkles: update : modify endpoint of getMyRunimoList api
jeeheaG Apr 10, 2025
7597223
:sparkles: add : make getRunimoTypeList api method in RunimoController
jeeheaG Apr 10, 2025
2d2178e
:sparkles: add : make empty getRunimoTypeList method in RunimoUsecase
jeeheaG Apr 10, 2025
effd52e
:sparkles: add : make RunimoSimpleModel
jeeheaG Apr 10, 2025
4fe1cf5
Merge remote-tracking branch 'origin/main' into feat/get-runimos
jeeheaG Apr 10, 2025
8530bc0
:sparkles: update : delete isMainRunimo field from RunimoSimpleModel
jeeheaG Apr 10, 2025
ca04d33
:sparkles: update : modify getMyRunimoList usecase
jeeheaG Apr 10, 2025
4e13984
:white_check_mark: test : modify getMyRunimoList api test case
jeeheaG Apr 10, 2025
d36f3a2
:white_check_mark: test : edit test data
jeeheaG Apr 10, 2025
49a5ad2
:sparkles: add : edit RunimoTypeSimpleModel and make jpa method
jeeheaG Apr 10, 2025
46b5edd
:sparkles: add : make getRunimoTypeList usecase logic
jeeheaG Apr 10, 2025
3777955
:white_check_mark: test : make getRunimoTypeList test case
jeeheaG Apr 10, 2025
bb3d19a
:bug: fix : change egg type response to Korean
jeeheaG Apr 11, 2025
aa936ab
:white_check_mark: test : change test of egg type response
jeeheaG Apr 11, 2025
6456de4
:memo: docs : edit swagger docs
jeeheaG Apr 11, 2025
de8aa69
:bug: update : add runimo grouping in GetRunimoTypeListResponse
jeeheaG Apr 11, 2025
b0359ee
:bug: update : add runimo grouping logic
jeeheaG Apr 11, 2025
8412e24
:test_tube: test : modify test
jeeheaG Apr 11, 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.common.response.SuccessResponse;
import org.runimo.runimo.hatch.controller.dto.response.HatchEggResponse;
Expand All @@ -15,6 +16,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "HATCH", description = "알 부화 API")
@RequiredArgsConstructor
@RestController
public class HatchController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ public enum HatchHttpResponseCode implements CustomResponseCode {
HATCH_EGG_NOT_READY("HEH4001", "부화 요청 알이 부화 가능한 상태가 아님", "부화 요청 알이 부화 가능한 상태가 아님",
HttpStatus.BAD_REQUEST),
HATCH_EGG_NOT_FOUND("HEH4041", "부화 요청 알이 존재하지 않음", "부화 요청 알이 존재하지 않음", HttpStatus.NOT_FOUND),
HATCH_USER_NOT_FOUND("HEH4042", "부화 요청 사용자가 존재하지 않음", "부화 요청 사용자가 존재하지 않음",
HttpStatus.NOT_FOUND),


// TODO : 다르게 처리할 방법 고민해보기
HATCH_RUNIMO_NOT_FOUND_INTERNAL_ERROR("HEH5001", "[서버 내부 오류] 부화될 러니모가 존재하지 않음",
"[서버 내부 오류] 부화될 러니모 존재하지 않음", HttpStatus.INTERNAL_SERVER_ERROR),
HATCH_EGG_TYPE_NOT_FOUND_INTERNAL_ERROR("HEH5002", "[서버 내부 오류] 부화될 알 종류가 존재하지 않음",
"[서버 내부 오류] 부화될 알 종류가 존재하지 않음", HttpStatus.INTERNAL_SERVER_ERROR),
;
"[서버 내부 오류] 부화될 알 종류가 존재하지 않음", HttpStatus.INTERNAL_SERVER_ERROR);


private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
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.GetRunimoTypeListResponse;
import org.runimo.runimo.runimo.controller.dto.response.SetMainRunimoResponse;
import org.runimo.runimo.runimo.exception.RunimoHttpResponseCode;
import org.runimo.runimo.runimo.service.usecase.RunimoUsecase;
Expand All @@ -16,6 +18,7 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "RUNIMO", description = "러니모 관련 API")
@RequiredArgsConstructor
@RestController
public class RunimoController {
Expand All @@ -26,7 +29,7 @@ public class RunimoController {
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "[MSH2001] 나의 보유 러니모 목록 조회 성공")
})
@GetMapping("/api/v1/runimos/my")
@GetMapping("/api/v1/users/me/runimos")
public ResponseEntity<SuccessResponse<GetMyRunimoListResponse>> getMyRunimoList(
@UserId Long userId) {
GetMyRunimoListResponse response = runimoUsecase.getMyRunimoList(userId);
Expand All @@ -38,6 +41,21 @@ public ResponseEntity<SuccessResponse<GetMyRunimoListResponse>> getMyRunimoList(
);
}

@Operation(summary = "전체 러니모 종류 조회", description = "전체 러니모 종류를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "[MSH2003] 전체 러니모 종류 조회 성공")
})
@GetMapping("/api/v1/runimos/types/all")
public ResponseEntity<SuccessResponse<GetRunimoTypeListResponse>> getRunimoTypeList() {
GetRunimoTypeListResponse response = runimoUsecase.getRunimoTypeList();

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

@Operation(summary = "대표 러니모 설정", description = "사용자의 대표 러니모를 설정합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "[MSH2002] 대표 러니모 설정 성공"),
Expand Down
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 GetRunimoTypeListResponse(
List<RunimoTypeGroup> runimoGroups
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@ 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 = "0")
Long totalRunCount,

@Schema(description = "함께한 러닝 누적 거리", example = "0")
Long totalDistanceInMeters,

@Schema(description = "러니모 상세설명", example = "마당알에서 태어난 마당 토끼예요.")
String description
@Schema(description = "메인 러니모 여부", example = "true")
Boolean isMainRunimo
) {

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

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;

@Schema(description = "전체 러니모 종류 조회 응답")
public record RunimoTypeGroup(
@Schema(description = "러니모가 태어난 알 속성", example = "마당")
String eggType,

@Schema(description = "해당 알에서 태어나는 러니모 목록")
List<RunimoTypeInfo> runimoTypes
) {

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

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

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

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

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

@Schema(description = "러니모 상세설명", example = "마당알에서 태어난 마당 토끼예요.")
String description
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
public enum RunimoHttpResponseCode implements CustomResponseCode {
GET_MY_RUNIMO_LIST_SUCCESS("MSH2001", "보유 러니모 목록 조회 성공", "보유 러니모 목록 조회 성공", HttpStatus.OK),
SET_MAIN_RUNIMO_SUCCESS("MSH2002", "대표 러니모 설정 성공", "대표 러니모 설정 성공", HttpStatus.OK),
GET_ALL_RUNIMO_TYPE_LIST_SUCCESS("MSH2003", "전체 러니모 종류 조회 성공", "전체 러니모 종류 조회 성공",
HttpStatus.OK),

USER_DO_NOT_OWN_RUNIMO("MSH4031", "요청 러니모의 소유자가 아님", "요청 러니모의 소유자가 아님", HttpStatus.FORBIDDEN),
RUNIMO_NOT_FOUND("MSH4041", "요청 러니모가 존재하지 않음", "요청 러니모가 존재하지 않음", HttpStatus.NOT_FOUND);
RUNIMO_NOT_FOUND("MSH4041", "요청 러니모가 존재하지 않음", "요청 러니모가 존재하지 않음", HttpStatus.NOT_FOUND),
;

private final String code;
private final String clientMessage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package org.runimo.runimo.runimo.repository;

import java.util.List;
import java.util.Optional;
import org.runimo.runimo.runimo.domain.RunimoDefinition;
import org.runimo.runimo.runimo.service.model.RunimoTypeSimpleModel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface RunimoDefinitionRepository extends JpaRepository<RunimoDefinition, Long> {

Optional<RunimoDefinition> findByCode(String runimoCode);

@Query("""
select new org.runimo.runimo.runimo.service.model.RunimoTypeSimpleModel(rd.name, rd.imgUrl, rd.code, rd.type, rd.description)
from RunimoDefinition rd
""")
List<RunimoTypeSimpleModel> findAllToSimpleModel();
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface RunimoRepository extends JpaRepository<Runimo, Long> {
boolean existsByUserIdAndRunimoDefinitionId(Long UserId, Long runimoDefinitionId);

@Query("""
select new org.runimo.runimo.runimo.service.model.RunimoSimpleModel(r.id, rd.name, rd.imgUrl, rd.code, rd.type, rd.description)
select new org.runimo.runimo.runimo.service.model.RunimoSimpleModel(r.id, rd.code, r.totalRunCount, r.totalDistanceInMeters)
from Runimo r
join RunimoDefinition rd on rd.id = r.runimoDefinitionId
where r.userId = :userId
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,22 @@
package org.runimo.runimo.runimo.service.model;

import java.util.List;
import org.runimo.runimo.item.domain.EggType;
import org.runimo.runimo.runimo.controller.dto.response.RunimoInfo;

public record RunimoSimpleModel(
Long id,
String name,
String imgUrl,
String code,
String eggType,
String description
Long totalRunCount,
Long totalDistanceInMeters
) {

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 static List<RunimoInfo> toDtoList(List<RunimoSimpleModel> modelList, Long mainRunimoId) {
return modelList.stream().map(i ->
i.toDto(i.id.equals(mainRunimoId))
).toList();
}

public RunimoSimpleModel(Long id, String name, String imgUrl, String code, EggType eggType,
String description) {
this(id, name, imgUrl, code, eggType.name(), description);
}

public static List<RunimoInfo> toDtoList(List<RunimoSimpleModel> modelList) {
return modelList.stream().map(RunimoSimpleModel::toDto).toList();
}

private RunimoInfo toDto() {
return new RunimoInfo(id, name, imgUrl, code, eggType, description);
private RunimoInfo toDto(Boolean isMainRunimo) {
return new RunimoInfo(id, code, totalRunCount, totalDistanceInMeters, isMainRunimo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.runimo.runimo.runimo.service.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.runimo.runimo.item.domain.EggType;
import org.runimo.runimo.runimo.controller.dto.response.RunimoTypeGroup;
import org.runimo.runimo.runimo.controller.dto.response.RunimoTypeInfo;

public record RunimoTypeSimpleModel(
String name,
String imgUrl,
String code,
String eggType,
String description
) {

public RunimoTypeSimpleModel(String name, String imgUrl, String code, EggType eggType,
String description) {
this(name, imgUrl, code, eggType.getName(), description);
}

public static List<RunimoTypeGroup> toDtoList(List<RunimoTypeSimpleModel> modelList) {
Map<String, List<RunimoTypeInfo>> runimoDtoMap = toRunimoTypeMap(
modelList);

List<RunimoTypeGroup> runimoTypeGroups = toRunimoTypeGroups(
runimoDtoMap);

return runimoTypeGroups;
}

private RunimoTypeInfo toDto() {
return new RunimoTypeInfo(name, imgUrl, code, description);
}


/**
* EggType enum 클래스 순서 기준으로 EggType 별 러니모 그룹목록 생성
*/
private static List<RunimoTypeGroup> toRunimoTypeGroups(
Map<String, List<RunimoTypeInfo>> runimoDtoMap) {

List<RunimoTypeGroup> runimoTypeGroups = new ArrayList<>();
for (EggType eggType : EggType.values()) {
String key = eggType.getName();
runimoTypeGroups.add(new RunimoTypeGroup(key, runimoDtoMap.get(key)));
}

return runimoTypeGroups;
}
Comment on lines +42 to +52
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure order consistency for egg type groups

The method creates RunimoTypeGroup objects based on the order of EggType enum values, which is a good approach for maintaining a consistent order in the response. However, there's a potential issue if the runimoDtoMap doesn't contain an entry for one of the enum values.


🏁 Script executed:

#!/bin/bash
# Check if there's error handling for missing egg types in the map

# First, check if there's any null check when accessing runimoDtoMap.get(key)
rg "runimoDtoMap\.get\(" -A 3 -B 3

# Then check if all EggType values are expected to be present in the database
echo "Checking for SQL scripts that populate EggType data:"
fd -e sql | xargs grep -l "EggType" | xargs cat

Length of output: 1049


Address Null Handling for Egg Type Map Entries

The current implementation correctly preserves the order by iterating over the EggType enum but doesn’t safeguard against null values when a key is missing in runimoDtoMap. This may lead to constructing a RunimoTypeGroup with a null list, potentially resulting in unexpected behavior downstream. Consider adding a null-check or defaulting to an empty list (e.g., using runimoDtoMap.get(key) != null ? runimoDtoMap.get(key) : Collections.emptyList()) to make the code more robust.

  • File Affected: src/main/java/org/runimo/runimo/runimo/service/model/RunimoTypeSimpleModel.java (Lines 42-52)


/**
* EggType 별 러니모 분류 map 생성
*/
private static Map<String, List<RunimoTypeInfo>> toRunimoTypeMap(
List<RunimoTypeSimpleModel> modelList) {

Map<String, List<RunimoTypeInfo>> runimoTypeDtos = new HashMap<>();
for (RunimoTypeSimpleModel model : modelList) {
String key = model.eggType();
if (!runimoTypeDtos.containsKey(key)) {
runimoTypeDtos.put(key, new ArrayList<>());
}
runimoTypeDtos.get(key).add(model.toDto());
}

return runimoTypeDtos;
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.runimo.runimo.runimo.service.usecase;

import org.runimo.runimo.runimo.controller.dto.response.GetMyRunimoListResponse;
import org.runimo.runimo.runimo.controller.dto.response.GetRunimoTypeListResponse;
import org.runimo.runimo.runimo.controller.dto.response.SetMainRunimoResponse;

public interface RunimoUsecase {

GetMyRunimoListResponse getMyRunimoList(Long userId);

SetMainRunimoResponse setMainRunimo(Long userId, Long runimoId);

GetRunimoTypeListResponse getRunimoTypeList();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
import java.util.List;
import java.util.NoSuchElementException;
import lombok.RequiredArgsConstructor;
import org.runimo.runimo.hatch.exception.HatchException;
import org.runimo.runimo.hatch.exception.HatchHttpResponseCode;
import org.runimo.runimo.runimo.controller.dto.response.GetMyRunimoListResponse;
import org.runimo.runimo.runimo.controller.dto.response.GetRunimoTypeListResponse;
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.RunimoDefinitionRepository;
import org.runimo.runimo.runimo.repository.RunimoRepository;
import org.runimo.runimo.runimo.service.model.RunimoSimpleModel;
import org.runimo.runimo.runimo.service.model.RunimoTypeSimpleModel;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.service.UserFinder;
import org.springframework.stereotype.Service;
Expand All @@ -21,13 +25,18 @@
@Service
public class RunimoUsecaseImpl implements RunimoUsecase {

private final RunimoDefinitionRepository runimoDefinitionRepository;
private final RunimoRepository runimoRepository;
private final UserFinder userFinder;
private final RunimoDefinitionRepository runimoDefinitionRepository;

public GetMyRunimoListResponse getMyRunimoList(Long userId) {
List<RunimoSimpleModel> runimos = runimoRepository.findAllByUserId(userId);
return new GetMyRunimoListResponse(RunimoSimpleModel.toDtoList(runimos));
List<RunimoSimpleModel> models = runimoRepository.findAllByUserId(userId);

User user = userFinder.findUserById(userId).orElseThrow(() -> HatchException.of(
HatchHttpResponseCode.HATCH_USER_NOT_FOUND));

return new GetMyRunimoListResponse(
RunimoSimpleModel.toDtoList(models, user.getMainRunimoId()));
}

@Transactional
Expand All @@ -44,4 +53,10 @@ public SetMainRunimoResponse setMainRunimo(Long userId, Long runimoId) {
return new SetMainRunimoResponse(runimoId);
}

@Override
public GetRunimoTypeListResponse getRunimoTypeList() {
List<RunimoTypeSimpleModel> models = runimoDefinitionRepository.findAllToSimpleModel();
return new GetRunimoTypeListResponse(RunimoTypeSimpleModel.toDtoList(models));
}

}
Loading