From f99b7af3fc35306120ed92c3eea8373e5a31d0c8 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Fri, 19 Jul 2024 17:05:30 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=ED=83=80=EC=9E=84=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=ED=8A=B9=EC=A0=95=20=EB=82=A0=EC=A7=9C=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95=EA=B8=B0=EB=8A=A5=20=EB=B0=8F=20=ED=8A=B9=EC=A0=95?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=20=EA=B8=B0=EC=A4=80=20=EC=9C=84/=EC=95=84?= =?UTF-8?q?=EB=9E=98=20=EB=AC=B4=ED=95=9C=EC=8A=A4=ED=81=AC=EB=A1=A4=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/repository/MemoryRepository.java | 3 - .../repository/MemoryRepositoryImpl.java | 60 ++++++------------- .../repository/MemoryRepositoryTest.java | 14 +++-- .../com/depromeet/memory/api/MemoryApi.java | 8 ++- .../memory/api/MemoryController.java | 11 ++-- .../memory/service/TimelineService.java | 9 ++- .../memory/service/TimelineServiceImpl.java | 40 +++++++++---- .../memory/mock/FakeMemoryRepository.java | 6 +- 8 files changed, 82 insertions(+), 69 deletions(-) diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java index 442640f2..c835d8e3 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java @@ -11,9 +11,6 @@ public interface MemoryRepository { Optional findById(Long memoryId); - Slice getSliceMemoryByMemberIdAndCursorId( - Long memberId, Long cursorId, LocalDate recordAt, Pageable pageable); - Slice findPrevMemoryByMemberId( Long memberId, Long cursorId, diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java index 419842f5..f2bdf8e8 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java @@ -35,30 +35,6 @@ public Optional findById(Long memoryId) { return memoryJpaRepository.findById(memoryId).map(MemoryEntity::toModel); } - @Override - public Slice getSliceMemoryByMemberIdAndCursorId( - Long memberId, Long cursorId, LocalDate recordAt, Pageable pageable) { - List result = - queryFactory - .selectFrom(memory) - .where( - memory.member.id.eq(memberId), - ltCursorIdOrRecordAt(cursorId, recordAt)) - .limit(pageable.getPageSize() + 1) - .orderBy(memory.recordAt.desc(), memory.id.desc()) - .fetch(); - List content = result.stream().map(MemoryEntity::toModel).toList(); - - boolean hasPrev = false; - if (content.size() > pageable.getPageSize()) { - content = new ArrayList<>(content); // immutable -> modifiedList - content.removeLast(); - hasPrev = true; - } - - return new SliceImpl<>(content, pageable, hasPrev); - } - // ---- 날짜 선택 후 위아래 무한 스크롤 구현 @Override @@ -74,14 +50,16 @@ public Slice findPrevMemoryByMemberId( .selectFrom(memory) .where( memory.member.id.eq(memberId), - ltCursorIdOrRecordAt(cursorId, recordAt)) + ltCursorIdOrCursorRecordAt(cursorId, cursorRecordAt), + loeRecordAt(recordAt)) + .limit(pageable.getPageSize() + 1) .orderBy(memory.recordAt.desc()) .fetch(); List content = toModel(result); boolean hasPrev = false; if (content.size() > pageable.getPageSize()) { - content = new ArrayList<>(content); // immutable -> modifiedList + content = new ArrayList<>(content); content.removeLast(); hasPrev = true; } @@ -93,18 +71,16 @@ public Slice findPrevMemoryByMemberId( public Slice findNextMemoryByMemberId( Long memberId, Long cursorId, - LocalDate cursorRecordAt, + LocalDate cursorRecordAt, // 7/20 이라 가정 Pageable pageable, LocalDate recordAt) { List result = queryFactory .selectFrom(memory) .where( - memory.member - .id - .eq(memberId) - .and(gtCursorId(cursorId)) - .and(goeRecordAt(recordAt))) + memory.member.id.eq(memberId), + gtCursorIdOrCursorRecordAt(cursorId, cursorRecordAt), + goeRecordAt(recordAt)) .limit(pageable.getPageSize() + 1) .orderBy(memory.recordAt.asc()) .fetch(); @@ -113,7 +89,7 @@ public Slice findNextMemoryByMemberId( boolean hasNext = false; if (content.size() > pageable.getPageSize()) { - content = new ArrayList<>(content); // immutable -> modifiedList + content = new ArrayList<>(content); content.removeLast(); hasNext = true; } @@ -122,7 +98,7 @@ public Slice findNextMemoryByMemberId( return new SliceImpl<>(content, pageable, hasNext); } - private BooleanExpression ltCursorIdOrRecordAt(Long cursorId, LocalDate recordAt) { + private BooleanExpression ltCursorIdOrCursorRecordAt(Long cursorId, LocalDate recordAt) { if (cursorId == null || recordAt == null) { return null; } @@ -138,18 +114,20 @@ private BooleanExpression loeRecordAt(LocalDate recordAt) { return memory.recordAt.loe(recordAt); } - private BooleanExpression gtCursorId(Long cursorId) { - if (cursorId != null) { - return memory.id.gt(cursorId); + private BooleanExpression gtCursorIdOrCursorRecordAt(Long cursorId, LocalDate cursorRecordAt) { + if (cursorId == null || cursorRecordAt == null) { + return null; } - return null; + return memory.recordAt + .gt(cursorRecordAt) + .or(memory.recordAt.eq(cursorRecordAt).and(memory.id.gt(cursorId))); } private BooleanExpression goeRecordAt(LocalDate recordAt) { - if (recordAt != null) { - return memory.recordAt.goe(recordAt); + if (recordAt == null) { + return null; } - return null; + return memory.recordAt.goe(recordAt); } private List toModel(List memoryEntities) { diff --git a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java index 53950ca5..e52e2724 100644 --- a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java +++ b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java @@ -67,14 +67,18 @@ private Pageable getPageable() { } @Test - void getSliceMemoryByMemberIdAndCursorId가_최신_30일_데이터_recordAt_Desc로_가져오는지_테스트() { + void findPrevMemoryByMemberId로_최근_날짜_이전_30일_recordAt_Desc로_가져오는지_테스트() { // when Slice resultSlice = - memoryRepositoryImpl.getSliceMemoryByMemberIdAndCursorId( - member.getId(), null, null, pageable); + memoryRepositoryImpl.findPrevMemoryByMemberId( + member.getId(), null, null, pageable, null); List result = resultSlice.getContent(); + List resultRecordAtList = result.stream().map(Memory::getRecordAt).toList(); + System.out.println(resultRecordAtList); + Memory lastMemory = result.getLast(); + // then assertThat(result.size()).isEqualTo(30); assertThat(lastMemory.getRecordAt()).isEqualTo(startRecordAt.minusDays(30)); @@ -90,12 +94,14 @@ private Pageable getPageable() { memoryRepositoryImpl.findPrevMemoryByMemberId( member.getId(), null, null, pageable, recordAt); List result = resultSlice.getContent(); + List resultRecordAtList = result.stream().map(Memory::getRecordAt).toList(); + System.out.println(resultRecordAtList); Memory lastMemory = result.getLast(); // then assertThat(result.size()).isEqualTo(30); - assertThat(lastMemory.getRecordAt()).isEqualTo(LocalDate.of(2024, 7, 1).plusDays(30)); + assertThat(lastMemory.getRecordAt()).isEqualTo(recordAt.minusDays(29)); } @Test diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java index e283a610..f264f50c 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java @@ -19,10 +19,12 @@ public interface MemoryApi { @Operation(summary = "수영 기록 단일 조회") ApiResponse read(@PathVariable("memoryId") Long memoryId); - @Operation(summary = "타임라인 최신순 조회") - ApiResponse timeline( + @Operation(summary = "타임라인 최신순 조회 및 달력 조회 후, 위/아래 무한 스크롤 구현") + ApiResponse timelineCalendar( @LoginMember Long memberId, @RequestParam(value = "cursorId", required = false) Long cursorId, - @RequestParam(value = "recordAt", required = false) String recordAt, + @RequestParam(value = "cursorRecordAt", required = false) String cursorRecordAt, + @RequestParam(value = "date", required = false) String date, + @RequestParam(value = "showNewer", required = false) boolean showNewer, @RequestParam(value = "size") Integer size); } diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java index 26c334bc..5b4e06b2 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java @@ -40,14 +40,17 @@ public ApiResponse read(@PathVariable("memoryId") Long memoryId) return ApiResponse.success(MemorySuccessType.GET_RESULT_SUCCESS, memoryResponse); } - @GetMapping("/timeline") - public ApiResponse timeline( + @GetMapping("/timeline-calendar") + public ApiResponse timelineCalendar( @LoginMember Long memberId, @RequestParam(value = "cursorId", required = false) Long cursorId, - @RequestParam(value = "recordAt", required = false) String recordAt, + @RequestParam(value = "cursorRecordAt", required = false) String cursorRecordAt, + @RequestParam(value = "date", required = false) String date, + @RequestParam(value = "showNewer", required = false) boolean showNewer, @RequestParam(value = "size") Integer size) { CustomSliceResponse result = - timelineService.getTimelineByMemberIdAndCursor(memberId, cursorId, recordAt, size); + timelineService.getTimelineByMemberIdAndCursorAndDate( + memberId, cursorId, cursorRecordAt, date, showNewer, size); return ApiResponse.success(MemorySuccessType.GET_TIMELINE_SUCCESS, result); } } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineService.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineService.java index 550da855..5e049605 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineService.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineService.java @@ -5,8 +5,13 @@ import com.depromeet.memory.dto.response.TimelineResponseDto; public interface TimelineService { - CustomSliceResponse getTimelineByMemberIdAndCursor( - Long memberId, Long cursorId, String recordAt, int pageSize); + CustomSliceResponse getTimelineByMemberIdAndCursorAndDate( + Long memberId, + Long cursorId, + String cursorRecordAt, + String date, + boolean showNewer, + int size); TimelineResponseDto mapToTimelineResponseDto(Memory memory); } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java index 7b9911bd..77494201 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java @@ -29,22 +29,40 @@ public class TimelineServiceImpl implements TimelineService { private final MemoryRepository memoryRepository; @Override - public CustomSliceResponse getTimelineByMemberIdAndCursor( - Long memberId, Long cursorId, String recordAt, int pageSize) { - LocalDate recordAtLocalDate = null; - if (recordAt != null && !recordAt.trim().isEmpty()) { - recordAtLocalDate = LocalDate.parse(recordAt); + public CustomSliceResponse getTimelineByMemberIdAndCursorAndDate( + Long memberId, + Long cursorId, + String cursorRecordAt, + String date, + boolean showNewer, + int size) { + Slice memories; + Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Order.desc("recordAt"))); + + LocalDate parsedCursorRecordAt = getLocalDateOrNull(cursorRecordAt); + LocalDate parsedDate = getLocalDateOrNull(date); + + if (showNewer) { + memories = + memoryRepository.findNextMemoryByMemberId( + memberId, cursorId, parsedCursorRecordAt, pageable, parsedDate); + } else { + memories = + memoryRepository.findPrevMemoryByMemberId( + memberId, cursorId, parsedCursorRecordAt, pageable, parsedDate); } - - Pageable pageable = PageRequest.of(0, pageSize, Sort.by(Sort.Order.desc("recordAt"))); - Slice memories = - memoryRepository.getSliceMemoryByMemberIdAndCursorId( - memberId, cursorId, recordAtLocalDate, pageable); - Slice result = memories.map(this::mapToTimelineResponseDto); return mapToCustomSliceResponse(result); } + private LocalDate getLocalDateOrNull(String cursorRecordAt) { + LocalDate cursorRecordAtLocalDate = null; + if (cursorRecordAt != null && !cursorRecordAt.trim().isEmpty()) { + cursorRecordAtLocalDate = LocalDate.parse(cursorRecordAt); + } + return cursorRecordAtLocalDate; + } + @Override public TimelineResponseDto mapToTimelineResponseDto(Memory memory) { return TimelineResponseDto.builder() diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java index 9affd7d1..d58a0fc1 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java @@ -44,7 +44,11 @@ public Optional findById(Long memoryId) { @Override public Slice getSliceMemoryByMemberIdAndCursorId( - Long memberId, Long cursorId, LocalDate recordAt, Pageable pageable) { + Long memberId, + Long cursorId, + LocalDate cursorRecordAt, + Pageable pageable, + LocalDate recordAt) { return null; } From 68b560335196aa727129ee15f3b7416e67926a9c Mon Sep 17 00:00:00 2001 From: penrose15 Date: Sun, 21 Jul 2024 22:49:46 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20refreshToken=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20(#74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/depromeet/member/Member.java | 4 +- .../depromeet/type/auth/AuthErrorType.java | 3 +- .../depromeet/type/auth/AuthSuccessType.java | 3 +- .../image/repository/ImageJpaRepository.java | 3 +- .../depromeet/member/entity/MemberEntity.java | 16 +++- .../java/com/depromeet/auth/api/AuthApi.java | 5 ++ .../depromeet/auth/api/AuthController.java | 10 +++ .../auth/dto/request/AccessTokenDto.java | 3 + .../auth/dto/response/RefreshTokenDto.java | 8 ++ .../depromeet/auth/service/AuthService.java | 3 + .../auth/service/AuthServiceImpl.java | 16 ++++ .../auth/service/JwtTokenService.java | 46 ++++++++-- .../filter/JwtAuthenticationFilter.java | 89 ++++++++++--------- 13 files changed, 153 insertions(+), 56 deletions(-) create mode 100644 module-presentation/src/main/java/com/depromeet/auth/dto/request/AccessTokenDto.java create mode 100644 module-presentation/src/main/java/com/depromeet/auth/dto/response/RefreshTokenDto.java diff --git a/module-domain/src/main/java/com/depromeet/member/Member.java b/module-domain/src/main/java/com/depromeet/member/Member.java index 15c1b6c1..7f38066e 100644 --- a/module-domain/src/main/java/com/depromeet/member/Member.java +++ b/module-domain/src/main/java/com/depromeet/member/Member.java @@ -13,12 +13,14 @@ public class Member { private String refreshToken; @Builder - public Member(Long id, Long goal, String name, String email, MemberRole role) { + public Member( + Long id, Long goal, String name, String email, MemberRole role, String refreshToken) { this.id = id; this.goal = goal; this.name = name; this.email = email; this.role = role; + this.refreshToken = refreshToken; } public void updateRefreshToken(String refreshToken) { diff --git a/module-independent/src/main/java/com/depromeet/type/auth/AuthErrorType.java b/module-independent/src/main/java/com/depromeet/type/auth/AuthErrorType.java index ec87f79c..62c06a59 100644 --- a/module-independent/src/main/java/com/depromeet/type/auth/AuthErrorType.java +++ b/module-independent/src/main/java/com/depromeet/type/auth/AuthErrorType.java @@ -7,7 +7,8 @@ public enum AuthErrorType implements ErrorType { JWT_TOKEN_EXPIRED("AUTH_2", "만료된 JWT 토큰입니다"), REFRESH_TOKEN_NOT_MATCH("AUTH_3", "일치하지 않는 Refresh 토큰입니다"), LOGIN_FAILED("AUTH_4", "로그인에 실패하였습니다"), - NOT_FOUND("AUTH_5", "소셜로그인 계정 정보가 존재하지 않습니다"); + NOT_FOUND("AUTH_5", "소셜로그인 계정 정보가 존재하지 않습니다"), + REFRESH_TOKEN_NOT_FOUND("AUTH_6", "Refresh 토큰이 존재하지 않습니다"); private final String code; private final String message; diff --git a/module-independent/src/main/java/com/depromeet/type/auth/AuthSuccessType.java b/module-independent/src/main/java/com/depromeet/type/auth/AuthSuccessType.java index f92a8691..619f3bc3 100644 --- a/module-independent/src/main/java/com/depromeet/type/auth/AuthSuccessType.java +++ b/module-independent/src/main/java/com/depromeet/type/auth/AuthSuccessType.java @@ -3,7 +3,8 @@ import com.depromeet.type.SuccessType; public enum AuthSuccessType implements SuccessType { - LOGIN_SUCCESS("LOGIN_1", "로그인에 성공하였습니다"); + LOGIN_SUCCESS("LOGIN_1", "로그인에 성공하였습니다"), + GET_REFRESH_TOKEN_SUCCESS("LOGIN_2", "Refresh Token 획득에 성공하였습니다"); private final String code; diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/image/repository/ImageJpaRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/image/repository/ImageJpaRepository.java index 145cb6bf..55cfd570 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/image/repository/ImageJpaRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/image/repository/ImageJpaRepository.java @@ -4,6 +4,7 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ImageJpaRepository extends JpaRepository { List findByMemoryId(Long memoryId); @@ -11,7 +12,7 @@ public interface ImageJpaRepository extends JpaRepository { @Query(value = """ select i from ImageEntity i where i.id in :ids """) - List findAllByIds(List ids); + List findAllByIds(@Param("ids") List ids); void deleteAllByMemoryId(Long memoryId); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java index 234976f4..9a7babee 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/member/entity/MemberEntity.java @@ -34,11 +34,12 @@ public class MemberEntity { @Column private String refreshToken; @Builder - private MemberEntity(Long id, String name, String email, MemberRole role) { + private MemberEntity(Long id, String name, String email, MemberRole role, String refreshToken) { this.id = id; this.name = name; this.email = email; this.role = role; + this.refreshToken = refreshToken; } public static MemberEntity from(Member member) { @@ -47,10 +48,21 @@ public static MemberEntity from(Member member) { .name(member.getName()) .email(member.getEmail()) .role(member.getRole()) + .refreshToken(getRefreshTokenOrNull(member)) .build(); } public Member toModel() { - return Member.builder().id(id).name(name).email(email).role(role).build(); + return Member.builder() + .id(id) + .name(name) + .email(email) + .role(role) + .refreshToken(refreshToken) + .build(); + } + + private static String getRefreshTokenOrNull(Member member) { + return member.getRefreshToken() != null ? member.getRefreshToken() : null; } } diff --git a/module-presentation/src/main/java/com/depromeet/auth/api/AuthApi.java b/module-presentation/src/main/java/com/depromeet/auth/api/AuthApi.java index 7d0e78e5..ff619d25 100644 --- a/module-presentation/src/main/java/com/depromeet/auth/api/AuthApi.java +++ b/module-presentation/src/main/java/com/depromeet/auth/api/AuthApi.java @@ -1,8 +1,10 @@ package com.depromeet.auth.api; +import com.depromeet.auth.dto.request.AccessTokenDto; import com.depromeet.auth.dto.request.GoogleLoginRequest; import com.depromeet.auth.dto.request.KakaoLoginRequest; import com.depromeet.auth.dto.response.JwtTokenResponseDto; +import com.depromeet.auth.dto.response.RefreshTokenDto; import com.depromeet.dto.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -18,4 +20,7 @@ ApiResponse loginByGoogle( @Operation(summary = "카카오 소셜로그인") ApiResponse loginByKakao( @Valid @RequestBody final KakaoLoginRequest request); + + @Operation(summary = "만료된 AccessToken으로 RefreshToken 전달") + ApiResponse getRefreshToken(@RequestBody final AccessTokenDto request); } diff --git a/module-presentation/src/main/java/com/depromeet/auth/api/AuthController.java b/module-presentation/src/main/java/com/depromeet/auth/api/AuthController.java index 360a7a16..f565c763 100644 --- a/module-presentation/src/main/java/com/depromeet/auth/api/AuthController.java +++ b/module-presentation/src/main/java/com/depromeet/auth/api/AuthController.java @@ -1,8 +1,10 @@ package com.depromeet.auth.api; +import com.depromeet.auth.dto.request.AccessTokenDto; import com.depromeet.auth.dto.request.GoogleLoginRequest; import com.depromeet.auth.dto.request.KakaoLoginRequest; import com.depromeet.auth.dto.response.JwtTokenResponseDto; +import com.depromeet.auth.dto.response.RefreshTokenDto; import com.depromeet.auth.service.AuthService; import com.depromeet.dto.response.ApiResponse; import com.depromeet.type.auth.AuthSuccessType; @@ -32,4 +34,12 @@ public ApiResponse loginByKakao( return ApiResponse.success( AuthSuccessType.LOGIN_SUCCESS, authService.loginByKakao(request)); } + + @PostMapping("/refresh") + public ApiResponse getRefreshToken(@RequestBody AccessTokenDto request) { + RefreshTokenDto refreshTokenDto = + authService.getRefreshTokenFromExpiredAccessToken(request.accessToken()); + + return ApiResponse.success(AuthSuccessType.LOGIN_SUCCESS, refreshTokenDto); + } } diff --git a/module-presentation/src/main/java/com/depromeet/auth/dto/request/AccessTokenDto.java b/module-presentation/src/main/java/com/depromeet/auth/dto/request/AccessTokenDto.java new file mode 100644 index 00000000..a33f8b7b --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/auth/dto/request/AccessTokenDto.java @@ -0,0 +1,3 @@ +package com.depromeet.auth.dto.request; + +public record AccessTokenDto(String accessToken) {} diff --git a/module-presentation/src/main/java/com/depromeet/auth/dto/response/RefreshTokenDto.java b/module-presentation/src/main/java/com/depromeet/auth/dto/response/RefreshTokenDto.java new file mode 100644 index 00000000..de7b6bbe --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/auth/dto/response/RefreshTokenDto.java @@ -0,0 +1,8 @@ +package com.depromeet.auth.dto.response; + +import lombok.Builder; + +public record RefreshTokenDto(String refreshToken) { + @Builder + public RefreshTokenDto {} +} diff --git a/module-presentation/src/main/java/com/depromeet/auth/service/AuthService.java b/module-presentation/src/main/java/com/depromeet/auth/service/AuthService.java index c2adb785..da6ca472 100644 --- a/module-presentation/src/main/java/com/depromeet/auth/service/AuthService.java +++ b/module-presentation/src/main/java/com/depromeet/auth/service/AuthService.java @@ -3,10 +3,13 @@ import com.depromeet.auth.dto.request.GoogleLoginRequest; import com.depromeet.auth.dto.request.KakaoLoginRequest; import com.depromeet.auth.dto.response.JwtTokenResponseDto; +import com.depromeet.auth.dto.response.RefreshTokenDto; public interface AuthService { JwtTokenResponseDto loginByGoogle(GoogleLoginRequest request); JwtTokenResponseDto loginByKakao(KakaoLoginRequest request); + + RefreshTokenDto getRefreshTokenFromExpiredAccessToken(String accessToken); } diff --git a/module-presentation/src/main/java/com/depromeet/auth/service/AuthServiceImpl.java b/module-presentation/src/main/java/com/depromeet/auth/service/AuthServiceImpl.java index d4785420..065494a2 100644 --- a/module-presentation/src/main/java/com/depromeet/auth/service/AuthServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/auth/service/AuthServiceImpl.java @@ -1,13 +1,18 @@ package com.depromeet.auth.service; +import static com.depromeet.security.constant.SecurityConstant.BEARER_PREFIX; +import static com.depromeet.type.auth.AuthErrorType.INVALID_JWT_TOKEN; + import com.depromeet.auth.dto.request.GoogleLoginRequest; import com.depromeet.auth.dto.request.KakaoLoginRequest; import com.depromeet.auth.dto.response.AccountProfileResponse; import com.depromeet.auth.dto.response.JwtTokenResponseDto; import com.depromeet.auth.dto.response.KakaoAccountProfileResponse; +import com.depromeet.auth.dto.response.RefreshTokenDto; import com.depromeet.auth.util.GoogleClient; import com.depromeet.auth.util.KakaoClient; import com.depromeet.exception.NotFoundException; +import com.depromeet.exception.UnauthorizedException; import com.depromeet.member.Member; import com.depromeet.member.service.MemberService; import com.depromeet.type.auth.AuthErrorType; @@ -49,4 +54,15 @@ public JwtTokenResponseDto loginByKakao(KakaoLoginRequest request) { final Member member = memberService.findOrCreateMemberBy(account); return jwtTokenService.generateToken(member.getId(), member.getRole()); } + + @Override + public RefreshTokenDto getRefreshTokenFromExpiredAccessToken(String accessToken) { + if (!accessToken.startsWith(BEARER_PREFIX.getValue())) { + throw new UnauthorizedException(INVALID_JWT_TOKEN); + } + + String refreshToken = + BEARER_PREFIX.getValue() + jwtTokenService.getRefreshToken(accessToken); + return RefreshTokenDto.builder().refreshToken(refreshToken).build(); + } } diff --git a/module-presentation/src/main/java/com/depromeet/auth/service/JwtTokenService.java b/module-presentation/src/main/java/com/depromeet/auth/service/JwtTokenService.java index daf325df..e7371245 100644 --- a/module-presentation/src/main/java/com/depromeet/auth/service/JwtTokenService.java +++ b/module-presentation/src/main/java/com/depromeet/auth/service/JwtTokenService.java @@ -30,6 +30,13 @@ public JwtTokenResponseDto generateToken(Long memberId, MemberRole memberRole) { AccessTokenDto accessToken = jwtUtils.generateAccessToken(memberId, memberRole); RefreshTokenDto refreshToken = jwtUtils.generateRefreshToken(memberId); + Member member = + memberRepository + .findById(memberId) + .orElseThrow(() -> new NotFoundException(MemberErrorType.NOT_FOUND)); + member.updateRefreshToken(refreshToken.refreshToken()); + memberRepository.save(member); + return new JwtTokenResponseDto( memberId, SecurityConstant.BEARER_PREFIX.getValue() + accessToken.accessToken(), @@ -44,16 +51,13 @@ public Optional parseRefreshToken(String token) { return jwtUtils.parseRefreshToken(token); } - public AccessTokenDto reissueAccessToken(String token) { - try { - return parseAccessToken(token) - .orElseThrow(() -> new UnauthorizedException(AuthErrorType.INVALID_JWT_TOKEN)); - } catch (ExpiredJwtException e) { - Long memberId = Long.parseLong(e.getClaims().getSubject()); - MemberRole memberRole = MemberRole.findByValue(e.getClaims().get("role", String.class)); + public AccessTokenDto reissueAccessToken(Long memberId) { + Member member = + memberRepository + .findById(memberId) + .orElseThrow(() -> new NotFoundException(MemberErrorType.NOT_FOUND)); - return jwtUtils.generateAccessToken(memberId, memberRole); - } + return jwtUtils.generateAccessToken(memberId, member.getRole()); } public RefreshTokenDto reissueRefreshToken(String token) { @@ -75,6 +79,30 @@ public RefreshTokenDto reissueRefreshToken(String token) { } } + public String getRefreshToken(String expiredAccessToken) { + expiredAccessToken = expiredAccessToken.substring(7); + Long memberId = null; + try { + parseAccessToken(expiredAccessToken) + .orElseThrow(() -> new UnauthorizedException(AuthErrorType.INVALID_JWT_TOKEN)); + } catch (ExpiredJwtException e) { + memberId = Long.parseLong(e.getClaims().getSubject()); + } + + if (memberId == null) { + throw new UnauthorizedException(AuthErrorType.INVALID_JWT_TOKEN); + } + + Member member = + memberRepository + .findById(memberId) + .orElseThrow(() -> new NotFoundException(MemberErrorType.NOT_FOUND)); + if (member.getRefreshToken() != null) { + return member.getRefreshToken(); + } + throw new NotFoundException(AuthErrorType.REFRESH_TOKEN_NOT_FOUND); + } + public RefreshTokenDto retrieveRefreshToken( RefreshTokenDto refreshTokenDto, String refreshToken) { Member member = diff --git a/module-presentation/src/main/java/com/depromeet/security/filter/JwtAuthenticationFilter.java b/module-presentation/src/main/java/com/depromeet/security/filter/JwtAuthenticationFilter.java index f743bb43..24c81e58 100644 --- a/module-presentation/src/main/java/com/depromeet/security/filter/JwtAuthenticationFilter.java +++ b/module-presentation/src/main/java/com/depromeet/security/filter/JwtAuthenticationFilter.java @@ -34,65 +34,72 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - log.info("start jwt filter"); String url = request.getRequestURI(); if (noAuthentication(url)) { filterChain.doFilter(request, response); return; } + Optional optionalAccessToken = Optional.ofNullable(request.getHeader(ACCESS_HEADER.getValue())); + Optional optionalRefreshToken = + Optional.ofNullable(request.getHeader(REFRESH_HEADER.getValue())); - if (optionalAccessToken.isEmpty()) { - log.info("access token is empty"); + if (optionalAccessToken.isEmpty() + && optionalRefreshToken.isEmpty()) { // accessToken, refreshToken 둘다 없는 경우 filterChain.doFilter(request, response); return; } + + if (optionalAccessToken.isEmpty()) { // accessToken 만 없는 경우 + reissueJWTWithRefreshToken(request, response, filterChain); + return; + } + String accessToken = optionalAccessToken.get(); if (!accessToken.startsWith(BEARER_PREFIX.getValue())) { - log.info("not starts with bearer "); filterChain.doFilter(request, response); return; } accessToken = accessToken.substring(7); - Optional optionalAccessTokenDto = parseAccessToken(accessToken); if (optionalAccessTokenDto.isPresent()) { - AccessTokenDto accessTokenDto = optionalAccessTokenDto.get(); + AccessTokenDto accessTokenDto = optionalAccessTokenDto.get(); // 액서스 토큰이 만료되지 않은 경우 setAuthentication(accessTokenDto); - } else { - // 클라이언트에서 refreshToken을 쿠키에 추가할 경우 - /* Optional optionalRefreshToken = Optional.ofNullable(WebUtils.getCookie(request, REFRESH_HEADER.getValue())) - .map(Cookie::getValue); - */ - // 그냥 헤더에 추가해서 보내줄 경우 - Optional optionalRefreshToken = - Optional.ofNullable(request.getHeader(REFRESH_HEADER.getValue())); - - if (optionalRefreshToken.isEmpty()) { - log.info("failed to find refresh token"); - filterChain.doFilter(request, response); - return; - } - String refreshToken = optionalRefreshToken.get(); - - Optional optionalRefreshTokenDto = parseRefreshToken(refreshToken); - - if (optionalRefreshTokenDto.isEmpty()) { - log.info("failed to parse refresh token"); - filterChain.doFilter(request, response); - return; - } - - RefreshTokenDto refreshTokenDto = optionalRefreshTokenDto.get(); - - jwtTokenService.retrieveRefreshToken(refreshTokenDto, refreshToken); - AccessTokenDto reissuedAccessToken = - addReissuedJwtTokenToHeader(response, accessToken, refreshToken); - setAuthentication(reissuedAccessToken); + } // 만료된 경우 401 error + filterChain.doFilter(request, response); + } + + private void reissueJWTWithRefreshToken( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + Optional optionalRefreshToken = + Optional.ofNullable(request.getHeader(REFRESH_HEADER.getValue())); + + if (optionalRefreshToken.isEmpty()) { + filterChain.doFilter(request, response); + return; + } + + String refreshToken = optionalRefreshToken.get(); + if (!refreshToken.startsWith(BEARER_PREFIX.getValue())) { + filterChain.doFilter(request, response); + return; + } + refreshToken = refreshToken.substring(7); + + Optional optionalRefreshTokenDto = parseRefreshToken(refreshToken); + if (optionalRefreshTokenDto.isEmpty()) { + filterChain.doFilter(request, response); + return; } + + RefreshTokenDto refreshTokenDto = optionalRefreshTokenDto.get(); + jwtTokenService.retrieveRefreshToken(refreshTokenDto, refreshToken); + AccessTokenDto reissuedAccessToken = addReissuedJwtTokenToHeader(response, refreshToken); + setAuthentication(reissuedAccessToken); filterChain.doFilter(request, response); } @@ -115,8 +122,8 @@ private Optional parseAccessToken(String accessToken) { log.error(e.getMessage()); throw new UnauthorizedException(INVALID_JWT_TOKEN); } catch (ExpiredJwtException e) { - log.error(e.getMessage()); - throw new UnauthorizedException(JWT_TOKEN_EXPIRED); + log.error("JWT expired"); + return Optional.empty(); } catch (Exception e) { log.error(e.getMessage()); throw e; @@ -142,12 +149,12 @@ private Optional parseRefreshToken(String refreshToken) { } private AccessTokenDto addReissuedJwtTokenToHeader( - HttpServletResponse response, String accessToken, String refreshToken) { - AccessTokenDto reissuedAccessToken = jwtTokenService.reissueAccessToken(accessToken); + HttpServletResponse response, String refreshToken) { RefreshTokenDto reissuedRefreshToken = jwtTokenService.reissueRefreshToken(refreshToken); + AccessTokenDto reissuedAccessToken = + jwtTokenService.reissueAccessToken(reissuedRefreshToken.memberId()); response.addHeader(ACCESS_HEADER.getValue(), reissuedAccessToken.accessToken()); - response.addHeader(REFRESH_HEADER.getValue(), reissuedRefreshToken.refreshToken()); return reissuedAccessToken; } From b6b6649001b45b55e72477451ea9bfd2854ae925 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Sun, 21 Jul 2024 22:50:34 +0900 Subject: [PATCH 03/11] fix: fix CustomSliceResponse (#50) --- .../depromeet/dto/response/CustomSliceResponse.java | 3 +-- .../memory/repository/MemoryDetailRepositoryImpl.java | 5 +++++ .../depromeet/memory/service/TimelineServiceImpl.java | 6 +++--- .../depromeet/memory/mock/FakeMemoryRepository.java | 10 ---------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java index b2244117..75c3116d 100644 --- a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java +++ b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java @@ -2,8 +2,7 @@ import lombok.Builder; -public record CustomSliceResponse( - T content, int pageNumber, int pageSize, boolean hasNext, boolean hasPrevious) { +public record CustomSliceResponse(T content, int pageNumber, int pageSize, boolean hasNext) { @Builder public CustomSliceResponse {} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java index 563712db..f9b5623d 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java @@ -2,6 +2,7 @@ import com.depromeet.memory.MemoryDetail; import com.depromeet.memory.entity.MemoryDetailEntity; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -14,4 +15,8 @@ public class MemoryDetailRepositoryImpl implements MemoryDetailRepository { public MemoryDetail save(MemoryDetail memoryDetail) { return memoryDetailJpaRepository.save(MemoryDetailEntity.from(memoryDetail)).toModel(); } + + public Optional findById(Long id) { + return memoryDetailJpaRepository.findById(id).map(MemoryDetailEntity::toModel); + } } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java index 77494201..adcae799 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java @@ -11,6 +11,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -133,7 +134,7 @@ private Integer calculateTotalMeter(List strokes, Short lane) { } private List strokeToDto(List strokes) { - if (strokes == null || strokes.isEmpty()) return null; + if (strokes == null || strokes.isEmpty()) return new ArrayList<>(); return strokes.stream() .map( @@ -156,7 +157,7 @@ private Integer getMeterFromStroke(Stroke stroke) { } private List imagesToDto(List images) { - if (images == null || images.isEmpty()) return null; + if (images == null || images.isEmpty()) return new ArrayList<>(); return images.stream() .map( @@ -177,7 +178,6 @@ private CustomSliceResponse mapToCustomSliceResponse(Slice findById(Long memoryId) { return data.stream().filter(item -> item.getId().equals(memoryId)).findAny(); } - @Override - public Slice getSliceMemoryByMemberIdAndCursorId( - Long memberId, - Long cursorId, - LocalDate cursorRecordAt, - Pageable pageable, - LocalDate recordAt) { - return null; - } - @Override public Slice findPrevMemoryByMemberId( Long memberId, From 8d44eee81428957786b18a3f101a0ae99f379d6d Mon Sep 17 00:00:00 2001 From: penrose15 Date: Sun, 21 Jul 2024 22:53:41 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix:=20=EC=BB=A8=ED=94=8C=EB=A6=AD?= =?UTF-8?q?=ED=8A=B8=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/depromeet/image/Image.java | 9 ++ .../java/com/depromeet/memory/Memory.java | 16 +++ .../java/com/depromeet/pool/FavoritePool.java | 19 ++++ .../java/com/depromeet/pool/PoolSearch.java | 19 ++++ .../type/common/CommonErrorType.java | 3 +- .../type/memory/MemoryDetailErrorType.java | 25 ++++ .../type/memory/MemoryErrorType.java | 3 +- .../type/memory/MemorySuccessType.java | 5 +- .../depromeet/type/pool/PoolErrorType.java | 4 +- .../depromeet/type/pool/PoolSuccessType.java | 3 +- .../memory/entity/MemoryDetailEntity.java | 8 ++ .../depromeet/memory/entity/MemoryEntity.java | 13 +++ .../repository/MemoryDetailRepository.java | 3 + .../MemoryDetailRepositoryImpl.java | 10 ++ .../memory/repository/MemoryRepository.java | 2 + .../repository/MemoryRepositoryImpl.java | 7 ++ .../memory/repository/StrokeRepository.java | 2 + .../repository/StrokeRepositoryImpl.java | 5 + .../pool/entity/FavoritePoolEntity.java | 60 ++++++++++ .../pool/entity/PoolSearchEntity.java | 60 ++++++++++ .../repository/FavoritePoolJpaRepository.java | 15 +++ .../pool/repository/PoolRepository.java | 16 +++ .../pool/repository/PoolRepositoryImpl.java | 57 +++++++++- .../repository/PoolSearchJpaRepository.java | 9 ++ .../common/GlobalExceptionAdvice.java | 12 +- .../image/service/ImageUpdateServiceImpl.java | 2 +- .../com/depromeet/memory/api/MemoryApi.java | 10 +- .../memory/api/MemoryController.java | 36 +++--- .../dto/request/MemoryUpdateRequest.java | 82 ++++++++++++++ .../dto/request/StrokeUpdateRequest.java | 9 ++ .../memory/dto/response/MemoryResponse.java | 78 ++++++------- .../depromeet/memory/facade/MemoryFacade.java | 62 ++++++++++ .../memory/service/MemoryService.java | 11 +- .../memory/service/MemoryServiceImpl.java | 107 +++++++++++++----- .../memory/service/StrokeService.java | 3 + .../memory/service/StrokeServiceImpl.java | 71 ++++++++++++ .../memory/service/TimelineServiceImpl.java | 2 +- .../java/com/depromeet/pool/api/PoolApi.java | 12 +- .../depromeet/pool/api/PoolController.java | 33 +++++- .../request/FavoritePoolCreateRequest.java | 7 ++ .../pool/dto/response/PoolInfoDto.java | 9 -- .../pool/dto/response/PoolInfoResponse.java | 21 ++++ .../dto/response/PoolInitialResponse.java | 35 ++++++ .../pool/dto/response/PoolResponseDto.java | 13 --- .../dto/response/PoolSearchInfoResponse.java | 12 ++ .../pool/dto/response/PoolSearchResponse.java | 13 +++ .../depromeet/pool/service/PoolService.java | 13 ++- .../pool/service/PoolServiceImpl.java | 79 ++++++++++++- .../depromeet/pool/service/PoolValidator.java | 13 +++ .../src/main/resources/application-local.yml | 2 +- .../mock/FakeMemoryDetailRepository.java | 31 +++++ .../memory/mock/FakeMemoryRepository.java | 51 +++++++++ .../memory/mock/FakePoolRepository.java | 35 ++++++ .../memory/mock/FakeStrokeRepository.java | 5 + .../memory/service/MemoryServiceTest.java | 77 ++++++++++++- .../memory/service/StrokeServiceTest.java | 3 +- 56 files changed, 1186 insertions(+), 136 deletions(-) create mode 100644 module-domain/src/main/java/com/depromeet/pool/FavoritePool.java create mode 100644 module-domain/src/main/java/com/depromeet/pool/PoolSearch.java create mode 100644 module-independent/src/main/java/com/depromeet/type/memory/MemoryDetailErrorType.java create mode 100644 module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/FavoritePoolEntity.java create mode 100644 module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/PoolSearchEntity.java create mode 100644 module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/FavoritePoolJpaRepository.java create mode 100644 module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolSearchJpaRepository.java create mode 100644 module-presentation/src/main/java/com/depromeet/memory/dto/request/MemoryUpdateRequest.java create mode 100644 module-presentation/src/main/java/com/depromeet/memory/dto/request/StrokeUpdateRequest.java create mode 100644 module-presentation/src/main/java/com/depromeet/memory/facade/MemoryFacade.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/request/FavoritePoolCreateRequest.java delete mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoDto.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoResponse.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInitialResponse.java delete mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolResponseDto.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchInfoResponse.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchResponse.java create mode 100644 module-presentation/src/main/java/com/depromeet/pool/service/PoolValidator.java diff --git a/module-domain/src/main/java/com/depromeet/image/Image.java b/module-domain/src/main/java/com/depromeet/image/Image.java index fb5d34db..c1c55e1d 100644 --- a/module-domain/src/main/java/com/depromeet/image/Image.java +++ b/module-domain/src/main/java/com/depromeet/image/Image.java @@ -32,4 +32,13 @@ public void addMemoryToImage(Memory memory) { public Optional getMemory() { return Optional.ofNullable(this.memory); } + + public Image withoutMemory() { + return Image.builder() + .id(id) + .originImageName(originImageName) + .imageName(imageName) + .imageUrl(imageUrl) + .build(); + } } diff --git a/module-domain/src/main/java/com/depromeet/memory/Memory.java b/module-domain/src/main/java/com/depromeet/memory/Memory.java index 841c3b53..11cca163 100644 --- a/module-domain/src/main/java/com/depromeet/memory/Memory.java +++ b/module-domain/src/main/java/com/depromeet/memory/Memory.java @@ -54,4 +54,20 @@ public void setStrokes(List strokes) { this.strokes = strokes; } } + + public Memory update(Memory updateMemory) { + return Memory.builder() + .id(id) + .member(member) + .pool(updateMemory.getPool()) + .memoryDetail(updateMemory.getMemoryDetail()) + .strokes(updateMemory.getStrokes()) + .images(images) + .recordAt(updateMemory.getRecordAt()) + .startTime(updateMemory.getStartTime()) + .endTime(updateMemory.getEndTime()) + .lane(updateMemory.getLane()) + .diary(updateMemory.getDiary()) + .build(); + } } diff --git a/module-domain/src/main/java/com/depromeet/pool/FavoritePool.java b/module-domain/src/main/java/com/depromeet/pool/FavoritePool.java new file mode 100644 index 00000000..312633ee --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/pool/FavoritePool.java @@ -0,0 +1,19 @@ +package com.depromeet.pool; + +import com.depromeet.member.Member; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class FavoritePool { + private Long id; + private Member member; + private Pool pool; + + @Builder + public FavoritePool(Long id, Member member, Pool pool) { + this.id = id; + this.member = member; + this.pool = pool; + } +} diff --git a/module-domain/src/main/java/com/depromeet/pool/PoolSearch.java b/module-domain/src/main/java/com/depromeet/pool/PoolSearch.java new file mode 100644 index 00000000..50999cb7 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/pool/PoolSearch.java @@ -0,0 +1,19 @@ +package com.depromeet.pool; + +import com.depromeet.member.Member; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class PoolSearch { + private Long id; + private Member member; + private Pool pool; + + @Builder + public PoolSearch(Long id, Member member, Pool pool) { + this.id = id; + this.member = member; + this.pool = pool; + } +} diff --git a/module-independent/src/main/java/com/depromeet/type/common/CommonErrorType.java b/module-independent/src/main/java/com/depromeet/type/common/CommonErrorType.java index 0167c549..0c77a9db 100644 --- a/module-independent/src/main/java/com/depromeet/type/common/CommonErrorType.java +++ b/module-independent/src/main/java/com/depromeet/type/common/CommonErrorType.java @@ -8,7 +8,8 @@ public enum CommonErrorType implements ErrorType { INVALID_MISSING_HEADER("COMMON_3", "요청에 필요한 헤더값이 존재하지 않습니다"), INVALID_HTTP_REQUEST("COMMON_4", "허용되지 않는 문자열이 입력되었습니다"), METHOD_NOT_ALLOWED("COMMON_5", "잘못된 HTTP 메소드의 요청입니다"), - INTERNAL_SERVER("COMMON_6", "알 수 없는 서버 에러가 발생했습니다"); + INTERNAL_SERVER("COMMON_6", "알 수 없는 서버 에러가 발생했습니다"), + VALIDATION_FAILED("COMMON_7", "정상적이지 않은 입력값입니다"); private final String code; private final String message; diff --git a/module-independent/src/main/java/com/depromeet/type/memory/MemoryDetailErrorType.java b/module-independent/src/main/java/com/depromeet/type/memory/MemoryDetailErrorType.java new file mode 100644 index 00000000..c6697a6d --- /dev/null +++ b/module-independent/src/main/java/com/depromeet/type/memory/MemoryDetailErrorType.java @@ -0,0 +1,25 @@ +package com.depromeet.type.memory; + +import com.depromeet.type.ErrorType; + +public enum MemoryDetailErrorType implements ErrorType { + NOT_FOUND("MEMORY_DETAIL_1", "기록 상세 정보가 존재하지 않습니다"); + + MemoryDetailErrorType(String code, String message) { + this.code = code; + this.message = message; + } + + final String code; + final String message; + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-independent/src/main/java/com/depromeet/type/memory/MemoryErrorType.java b/module-independent/src/main/java/com/depromeet/type/memory/MemoryErrorType.java index 67cd88d6..bb831cdb 100644 --- a/module-independent/src/main/java/com/depromeet/type/memory/MemoryErrorType.java +++ b/module-independent/src/main/java/com/depromeet/type/memory/MemoryErrorType.java @@ -4,7 +4,8 @@ public enum MemoryErrorType implements ErrorType { CREATE_FAILED("MEMORY_1", "기록 저장에 실패하였습니다"), - NOT_FOUND("MEMORY_2", "기록이 존재하지 않습니다"); + NOT_FOUND("MEMORY_2", "기록이 존재하지 않습니다"), + UPDATE_FAILED("MEMORY_3", "작성자가 아니라면 기록을 수정할 수 없습니다"); private final String code; private final String message; diff --git a/module-independent/src/main/java/com/depromeet/type/memory/MemorySuccessType.java b/module-independent/src/main/java/com/depromeet/type/memory/MemorySuccessType.java index 68693723..3c59e9d6 100644 --- a/module-independent/src/main/java/com/depromeet/type/memory/MemorySuccessType.java +++ b/module-independent/src/main/java/com/depromeet/type/memory/MemorySuccessType.java @@ -4,8 +4,9 @@ public enum MemorySuccessType implements SuccessType { POST_RESULT_SUCCESS("MEMORY_1", "수영 기록 저장에 성공하였습니다"), - GET_TIMELINE_SUCCESS("MEMORY_2", "타임라인 조회에 성공하였습니다"), - GET_RESULT_SUCCESS("MEMORY_3", "수영 기록 조회에 성공하였습니다"); + GET_RESULT_SUCCESS("MEMORY_2", "수영 기록 조회에 성공하였습니다"), + PATCH_RESULT_SUCCESS("MEMORY_3", "수영 기록 수정에 성공하였습니다"), + GET_TIMELINE_SUCCESS("MEMORY_4", "타임라인 조회에 성공하였습니다"); private final String code; diff --git a/module-independent/src/main/java/com/depromeet/type/pool/PoolErrorType.java b/module-independent/src/main/java/com/depromeet/type/pool/PoolErrorType.java index 5e6d985e..076971cf 100644 --- a/module-independent/src/main/java/com/depromeet/type/pool/PoolErrorType.java +++ b/module-independent/src/main/java/com/depromeet/type/pool/PoolErrorType.java @@ -3,7 +3,9 @@ import com.depromeet.type.ErrorType; public enum PoolErrorType implements ErrorType { - NOT_FOUND("POOL_1", "수영장 정보가 존재하지 않습니다"); + NOT_FOUND("POOL_1", "수영장 정보가 존재하지 않습니다"), + FAVORITE_NOT_FOUND("POOL_2", "즐겨찾기한 수영장 정보가 존재하지 않습니다"), + FAVORITE_FORBIDDEN("POOL_3", "자신이 등록한 즐겨찾기 정보가 아닙니다"); private final String code; private final String message; diff --git a/module-independent/src/main/java/com/depromeet/type/pool/PoolSuccessType.java b/module-independent/src/main/java/com/depromeet/type/pool/PoolSuccessType.java index d7a04e57..1725155f 100644 --- a/module-independent/src/main/java/com/depromeet/type/pool/PoolSuccessType.java +++ b/module-independent/src/main/java/com/depromeet/type/pool/PoolSuccessType.java @@ -3,7 +3,8 @@ import com.depromeet.type.SuccessType; public enum PoolSuccessType implements SuccessType { - SEARCH_SUCCESS("POOL_1", "수영장 검색을 성공하였습니다"); + SEARCH_SUCCESS("POOL_1", "수영장 검색을 성공하였습니다"), + INITIAL_GET_SUCCESS("POOL_2", "즐겨찾기 및 최근 검색 수영장 조회를 성공하였습니다"); private final String code; private final String message; diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryDetailEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryDetailEntity.java index 999fa377..ac056ab1 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryDetailEntity.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryDetailEntity.java @@ -59,4 +59,12 @@ public MemoryDetail toModel() { .kcal(this.kcal) .build(); } + + public MemoryDetailEntity update(MemoryDetailEntity mde) { + if (mde.getItem() != null) this.item = mde.getItem(); + if (mde.getHeartRate() != null) this.heartRate = mde.getHeartRate(); + if (mde.getPace() != null) this.pace = mde.getPace(); + if (mde.getKcal() != null) this.kcal = mde.getKcal(); + return this; + } } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryEntity.java index d390be43..f7265d25 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryEntity.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/entity/MemoryEntity.java @@ -156,4 +156,17 @@ private MemoryDetail getMemoryDetailOrNull() { public String updateDiary(String diary) { return this.diary = diary; } + + public MemoryEntity update(MemoryEntity me) { + if (me.getPool() != null) this.pool = me.getPool(); + if (me.getMemoryDetail() != null) this.memoryDetail = me.getMemoryDetail(); + if (me.getStrokes() != null) this.strokes = me.getStrokes(); + if (me.getImages() != null) this.images = me.getImages(); + if (me.getRecordAt() != null) this.recordAt = me.getRecordAt(); + if (me.getStartTime() != null) this.startTime = me.getStartTime(); + if (me.getEndTime() != null) this.endTime = me.getEndTime(); + if (me.getLane() != null) this.lane = me.getLane(); + if (me.getDiary() != null) this.diary = me.getDiary(); + return this; + } } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepository.java index 3bbea2cf..b248a5f5 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepository.java @@ -1,7 +1,10 @@ package com.depromeet.memory.repository; import com.depromeet.memory.MemoryDetail; +import java.util.Optional; public interface MemoryDetailRepository { MemoryDetail save(MemoryDetail memoryDetail); + + Optional update(Long id, MemoryDetail updateMemoryDetail); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java index f9b5623d..ae3431bc 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryDetailRepositoryImpl.java @@ -16,6 +16,16 @@ public MemoryDetail save(MemoryDetail memoryDetail) { return memoryDetailJpaRepository.save(MemoryDetailEntity.from(memoryDetail)).toModel(); } + @Override + public Optional update(Long id, MemoryDetail updateMemoryDetail) { + return memoryDetailJpaRepository + .findById(id) + .map( + entity -> + entity.update(MemoryDetailEntity.from(updateMemoryDetail)) + .toModel()); + } + public Optional findById(Long id) { return memoryDetailJpaRepository.findById(id).map(MemoryDetailEntity::toModel); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java index c835d8e3..11bcddaa 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java @@ -11,6 +11,8 @@ public interface MemoryRepository { Optional findById(Long memoryId); + Optional update(Long memoryId, Memory memoryUpdate); + Slice findPrevMemoryByMemberId( Long memberId, Long cursorId, diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java index f2bdf8e8..a7f618f1 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java @@ -35,6 +35,13 @@ public Optional findById(Long memoryId) { return memoryJpaRepository.findById(memoryId).map(MemoryEntity::toModel); } + @Override + public Optional update(Long memoryId, Memory memoryUpdate) { + return memoryJpaRepository + .findById(memoryId) + .map(entity -> entity.update(MemoryEntity.from(memoryUpdate)).toModel()); + } + // ---- 날짜 선택 후 위아래 무한 스크롤 구현 @Override diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepository.java index a73a11ab..35432d72 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepository.java @@ -7,4 +7,6 @@ public interface StrokeRepository { Stroke save(Stroke stroke); List findAllByMemoryId(Long memoryId); + + void deleteById(Long id); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepositoryImpl.java index 6ffbc925..228dd67c 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/StrokeRepositoryImpl.java @@ -22,4 +22,9 @@ public List findAllByMemoryId(Long memoryId) { .map(StrokeEntity::toModel) .toList(); } + + @Override + public void deleteById(Long id) { + strokeJpaRepository.deleteById(id); + } } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/FavoritePoolEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/FavoritePoolEntity.java new file mode 100644 index 00000000..52a28063 --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/FavoritePoolEntity.java @@ -0,0 +1,60 @@ +package com.depromeet.pool.entity; + +import com.depromeet.member.entity.MemberEntity; +import com.depromeet.pool.FavoritePool; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class FavoritePoolEntity { + @Id + @Column(name = "favorite_pool_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @JoinColumn(name = "member_id") + @ManyToOne(fetch = FetchType.LAZY) + private MemberEntity member; + + @NotNull + @JoinColumn(name = "pool_id") + @ManyToOne(fetch = FetchType.LAZY) + private PoolEntity pool; + + @Builder + public FavoritePoolEntity(Long id, MemberEntity member, PoolEntity pool) { + this.id = id; + this.member = member; + this.pool = pool; + } + + public static FavoritePoolEntity from(FavoritePool favoritePool) { + return FavoritePoolEntity.builder() + .id(favoritePool.getId()) + .member(MemberEntity.from(favoritePool.getMember())) + .pool(PoolEntity.from(favoritePool.getPool())) + .build(); + } + + public FavoritePool toModel() { + return FavoritePool.builder() + .id(this.id) + .member(this.member.toModel()) + .pool(this.pool.toModel()) + .build(); + } +} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/PoolSearchEntity.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/PoolSearchEntity.java new file mode 100644 index 00000000..f61fdf1e --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/entity/PoolSearchEntity.java @@ -0,0 +1,60 @@ +package com.depromeet.pool.entity; + +import com.depromeet.member.entity.MemberEntity; +import com.depromeet.pool.PoolSearch; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class PoolSearchEntity { + @Id + @Column(name = "pool_search_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @JoinColumn(name = "member_id") + @ManyToOne(fetch = FetchType.LAZY) + private MemberEntity member; + + @NotNull + @JoinColumn(name = "pool_id") + @ManyToOne(fetch = FetchType.LAZY) + private PoolEntity pool; + + @Builder + public PoolSearchEntity(Long id, MemberEntity member, PoolEntity pool) { + this.id = id; + this.member = member; + this.pool = pool; + } + + public static PoolSearchEntity from(PoolSearch poolSearch) { + return PoolSearchEntity.builder() + .id(poolSearch.getId()) + .member(MemberEntity.from(poolSearch.getMember())) + .pool(PoolEntity.from(poolSearch.getPool())) + .build(); + } + + public PoolSearch toModel() { + return PoolSearch.builder() + .id(this.id) + .member(this.member.toModel()) + .pool(this.pool.toModel()) + .build(); + } +} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/FavoritePoolJpaRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/FavoritePoolJpaRepository.java new file mode 100644 index 00000000..7881f7aa --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/FavoritePoolJpaRepository.java @@ -0,0 +1,15 @@ +package com.depromeet.pool.repository; + +import com.depromeet.member.entity.MemberEntity; +import com.depromeet.pool.entity.FavoritePoolEntity; +import com.depromeet.pool.entity.PoolEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FavoritePoolJpaRepository extends JpaRepository { + List findAllByMemberId(Long memberId); + + boolean existsByMemberIdAndPoolId(Long memberId, Long poolId); + + void deleteByMemberAndPool(MemberEntity member, PoolEntity pool); +} diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepository.java index 80380a78..951d33f9 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepository.java @@ -1,13 +1,29 @@ package com.depromeet.pool.repository; +import com.depromeet.pool.FavoritePool; import com.depromeet.pool.Pool; +import com.depromeet.pool.PoolSearch; import java.util.List; import java.util.Optional; public interface PoolRepository { List findPoolsByName(String nameQuery); + List findFavoritePools(Long memberId); + + List findSearchedPools(Long memberId); + Optional findById(Long poolId); + Optional findFavoritePoolById(Long favoritePoolId); + Pool save(Pool pool); + + PoolSearch savePoolSearch(PoolSearch poolSearch); + + FavoritePool saveFavoritePool(FavoritePool favoritePool); + + boolean existsFavoritePool(FavoritePool favoritePool); + + void deleteFavoritePool(FavoritePool favoritePool); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepositoryImpl.java index 4fb3de86..d040dae6 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolRepositoryImpl.java @@ -2,8 +2,13 @@ import static com.depromeet.pool.entity.QPoolEntity.*; +import com.depromeet.member.entity.MemberEntity; +import com.depromeet.pool.FavoritePool; import com.depromeet.pool.Pool; +import com.depromeet.pool.PoolSearch; +import com.depromeet.pool.entity.FavoritePoolEntity; import com.depromeet.pool.entity.PoolEntity; +import com.depromeet.pool.entity.PoolSearchEntity; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; @@ -14,17 +19,32 @@ @Repository @RequiredArgsConstructor public class PoolRepositoryImpl implements PoolRepository { - private final PoolJpaRepository poolJpaRepository; private final JPAQueryFactory queryFactory; + private final PoolJpaRepository poolJpaRepository; + private final PoolSearchJpaRepository poolSearchJpaRepository; + private final FavoritePoolJpaRepository favoritePoolJpaRepository; @Override public List findPoolsByName(String nameQuery) { List findPools = - queryFactory.selectFrom(poolEntity).where(nameLike(nameQuery)).limit(3).fetch(); + queryFactory.selectFrom(poolEntity).where(nameLike(nameQuery)).fetch(); return findPools.stream().map(PoolEntity::toModel).toList(); } + @Override + public List findFavoritePools(Long memberId) { + List favoritePools = + favoritePoolJpaRepository.findAllByMemberId(memberId); + return favoritePools.stream().map(FavoritePoolEntity::toModel).toList(); + } + + @Override + public List findSearchedPools(Long memberId) { + List searchedPools = poolSearchJpaRepository.findALlByMemberId(memberId); + return searchedPools.stream().map(PoolSearchEntity::toModel).toList(); + } + private BooleanExpression nameLike(String query) { BooleanExpression whereExpression = poolEntity.isNotNull(); @@ -40,8 +60,41 @@ public Optional findById(Long poolId) { return poolJpaRepository.findById(poolId).map(PoolEntity::toModel); } + @Override + public Optional findFavoritePoolById(Long favoritePoolId) { + return favoritePoolJpaRepository.findById(favoritePoolId).map(FavoritePoolEntity::toModel); + } + @Override public Pool save(Pool pool) { return poolJpaRepository.save(PoolEntity.from(pool)).toModel(); } + + @Override + public PoolSearch savePoolSearch(PoolSearch poolSearch) { + PoolSearchEntity poolSearchEntity = PoolSearchEntity.from(poolSearch); + return poolSearchJpaRepository.save(poolSearchEntity).toModel(); + } + + @Override + public FavoritePool saveFavoritePool(FavoritePool favoritePool) { + FavoritePoolEntity favoritePoolEntity = FavoritePoolEntity.from(favoritePool); + return favoritePoolJpaRepository.save(favoritePoolEntity).toModel(); + } + + @Override + public boolean existsFavoritePool(FavoritePool favoritePool) { + FavoritePoolEntity favoritePoolEntity = FavoritePoolEntity.from(favoritePool); + Long memberId = favoritePoolEntity.getMember().getId(); + Long poolId = favoritePoolEntity.getPool().getId(); + return favoritePoolJpaRepository.existsByMemberIdAndPoolId(memberId, poolId); + } + + @Override + public void deleteFavoritePool(FavoritePool favoritePool) { + FavoritePoolEntity favoritePoolEntity = FavoritePoolEntity.from(favoritePool); + MemberEntity member = favoritePoolEntity.getMember(); + PoolEntity pool = favoritePoolEntity.getPool(); + favoritePoolJpaRepository.deleteByMemberAndPool(member, pool); + } } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolSearchJpaRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolSearchJpaRepository.java new file mode 100644 index 00000000..25577e49 --- /dev/null +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/pool/repository/PoolSearchJpaRepository.java @@ -0,0 +1,9 @@ +package com.depromeet.pool.repository; + +import com.depromeet.pool.entity.PoolSearchEntity; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PoolSearchJpaRepository extends JpaRepository { + List findALlByMemberId(Long memberId); +} diff --git a/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java b/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java index 89d31ec9..f65e12df 100644 --- a/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java +++ b/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java @@ -5,6 +5,7 @@ import com.depromeet.type.common.CommonErrorType; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.ConstraintDefinitionException; +import jakarta.validation.ConstraintViolationException; import jakarta.validation.UnexpectedTypeException; import java.io.IOException; import java.util.HashMap; @@ -79,7 +80,16 @@ protected ResponseEntity> handlerConstraintDefinitionException( final ConstraintDefinitionException ex) { log.error(ex.getMessage()); return new ResponseEntity<>( - ApiResponse.fail(CommonErrorType.INVALID_HTTP_REQUEST, 400, ex.toString()), + ApiResponse.fail(CommonErrorType.VALIDATION_FAILED, 400, ex.toString()), + HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ConstraintViolationException.class) + protected ResponseEntity> handlerConstraintViolationException( + final ConstraintViolationException ex) { + log.error(ex.getMessage()); + return new ResponseEntity<>( + ApiResponse.fail(CommonErrorType.VALIDATION_FAILED, 400, ex.toString()), HttpStatus.BAD_REQUEST); } diff --git a/module-presentation/src/main/java/com/depromeet/image/service/ImageUpdateServiceImpl.java b/module-presentation/src/main/java/com/depromeet/image/service/ImageUpdateServiceImpl.java index 7477934d..f16d0f2d 100644 --- a/module-presentation/src/main/java/com/depromeet/image/service/ImageUpdateServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/image/service/ImageUpdateServiceImpl.java @@ -66,7 +66,7 @@ private List updateNewImages( for (MultipartFile image : images) { String originImageName = image.getOriginalFilename(); String imageName = generateImageName(originImageName); - updatedImageNames.add(imageName); + updatedImageNames.add(imageName); // 이게 if문 밑으로 내려가야 하지 않을까요? if (existImagesName.contains(imageName)) continue; diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java index f264f50c..0a866d97 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryApi.java @@ -2,6 +2,7 @@ import com.depromeet.dto.response.ApiResponse; import com.depromeet.memory.dto.request.MemoryCreateRequest; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; import com.depromeet.memory.dto.response.MemoryResponse; import com.depromeet.security.LoginMember; import io.swagger.v3.oas.annotations.Operation; @@ -14,11 +15,18 @@ @Tag(name = "수영 기록(Memory)") public interface MemoryApi { @Operation(summary = "수영 기록 저장") - ApiResponse create(@Valid @RequestBody MemoryCreateRequest memoryCreateRequest); + ApiResponse create( + @LoginMember Long memberId, + @Valid @RequestBody MemoryCreateRequest memoryCreateRequest); @Operation(summary = "수영 기록 단일 조회") ApiResponse read(@PathVariable("memoryId") Long memoryId); + @Operation(summary = "수영 기록 수정") + ApiResponse update( + @PathVariable("memoryId") Long memoryId, + @RequestBody MemoryUpdateRequest memoryUpdateRequest); + @Operation(summary = "타임라인 최신순 조회 및 달력 조회 후, 위/아래 무한 스크롤 구현") ApiResponse timelineCalendar( @LoginMember Long memberId, diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java index 5b4e06b2..9df53ffd 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java @@ -2,18 +2,13 @@ import com.depromeet.dto.response.ApiResponse; import com.depromeet.dto.response.CustomSliceResponse; -import com.depromeet.image.service.ImageUploadService; -import com.depromeet.memory.Memory; -import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.MemoryCreateRequest; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; import com.depromeet.memory.dto.response.MemoryResponse; -import com.depromeet.memory.service.MemoryService; -import com.depromeet.memory.service.StrokeService; -import com.depromeet.memory.service.TimelineService; +import com.depromeet.memory.facade.MemoryFacade; import com.depromeet.security.LoginMember; import com.depromeet.type.memory.MemorySuccessType; import jakarta.validation.Valid; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -21,23 +16,28 @@ @RequiredArgsConstructor @RequestMapping("/api/memory") public class MemoryController implements MemoryApi { - private final MemoryService memoryService; - private final StrokeService strokeService; - private final ImageUploadService imageUploadService; - private final TimelineService timelineService; + private final MemoryFacade memoryFacade; @PostMapping - public ApiResponse create(@Valid @RequestBody MemoryCreateRequest memoryCreateRequest) { - Memory newMemory = memoryService.save(memoryCreateRequest); - List strokes = strokeService.saveAll(newMemory, memoryCreateRequest.getStrokes()); - imageUploadService.addMemoryIdToImages(newMemory, memoryCreateRequest.getImageIdList()); + public ApiResponse create( + @LoginMember Long memberId, + @Valid @RequestBody MemoryCreateRequest memoryCreateRequest) { + memoryFacade.create(memberId, memoryCreateRequest); return ApiResponse.success(MemorySuccessType.POST_RESULT_SUCCESS); } @GetMapping("/{memoryId}") public ApiResponse read(@PathVariable("memoryId") Long memoryId) { - MemoryResponse memoryResponse = memoryService.findById(memoryId); - return ApiResponse.success(MemorySuccessType.GET_RESULT_SUCCESS, memoryResponse); + MemoryResponse response = memoryFacade.findById(memoryId); + return ApiResponse.success(MemorySuccessType.GET_RESULT_SUCCESS, response); + } + + @PatchMapping("/{memoryId}") + public ApiResponse update( + @PathVariable("memoryId") Long memoryId, + @Valid @RequestBody MemoryUpdateRequest memoryUpdateRequest) { + MemoryResponse response = memoryFacade.update(memoryId, memoryUpdateRequest); + return ApiResponse.success(MemorySuccessType.PATCH_RESULT_SUCCESS, response); } @GetMapping("/timeline-calendar") @@ -49,7 +49,7 @@ public ApiResponse timelineCalendar( @RequestParam(value = "showNewer", required = false) boolean showNewer, @RequestParam(value = "size") Integer size) { CustomSliceResponse result = - timelineService.getTimelineByMemberIdAndCursorAndDate( + memoryFacade.getTimelineByMemberIdAndCursorAndDate( memberId, cursorId, cursorRecordAt, date, showNewer, size); return ApiResponse.success(MemorySuccessType.GET_TIMELINE_SUCCESS, result); } diff --git a/module-presentation/src/main/java/com/depromeet/memory/dto/request/MemoryUpdateRequest.java b/module-presentation/src/main/java/com/depromeet/memory/dto/request/MemoryUpdateRequest.java new file mode 100644 index 00000000..dba95e97 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/memory/dto/request/MemoryUpdateRequest.java @@ -0,0 +1,82 @@ +package com.depromeet.memory.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +@Getter +@NoArgsConstructor +public class MemoryUpdateRequest { + @Schema(description = "수영장 정보 Id", example = "null") + private Long poolId; // 수영장 정보 + + // MemoryDetail + @Schema(description = "수영 장비", example = "오리발") + private String item; + + @Schema(description = "심박수", example = "129") + private Short heartRate; + + @Schema(description = "페이스", example = "05:00:00", maxLength = 8, type = "string") + private LocalTime pace; + + @Schema(description = "칼로리", example = "300") + private Integer kcal; + + // Memory + @DateTimeFormat(pattern = "yyyy-MM-dd") + @NotNull(message = "수영을 한 날짜를 입력하세요") + private LocalDate recordAt; + + @DateTimeFormat(pattern = "HH:mm:ss") + @NotNull(message = "수영을 시작한 시간을 입력하세요") + @Schema(description = "수영 시작 시간", example = "11:00:00", maxLength = 8, type = "string") + private LocalTime startTime; + + @DateTimeFormat(pattern = "HH:mm:ss") + @NotNull(message = "수영을 종료한 시간을 입력하세요") + @Schema(description = "수영 종료 시간", example = "11:50:00", maxLength = 8, type = "string") + private LocalTime endTime; + + @Schema(description = "레인 길이", example = "25") + private Short lane; + + @Schema(description = "수영 일기", example = "나는 짱이야!! 내가 정말 멋져!!") + private String diary; + + // Stroke + @Schema(description = "영법 목록") + private List strokes; + + @Builder + public MemoryUpdateRequest( + Long poolId, + String item, + Short heartRate, + LocalTime pace, + Integer kcal, + LocalDate recordAt, + LocalTime startTime, + LocalTime endTime, + Short lane, + String diary, + List strokes) { + this.poolId = poolId; + this.item = item; + this.heartRate = heartRate; + this.pace = pace; + this.kcal = kcal; + this.recordAt = recordAt; + this.startTime = startTime; + this.endTime = endTime; + this.lane = lane; + this.diary = diary; + this.strokes = strokes; + } +} diff --git a/module-presentation/src/main/java/com/depromeet/memory/dto/request/StrokeUpdateRequest.java b/module-presentation/src/main/java/com/depromeet/memory/dto/request/StrokeUpdateRequest.java new file mode 100644 index 00000000..ebaf16da --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/memory/dto/request/StrokeUpdateRequest.java @@ -0,0 +1,9 @@ +package com.depromeet.memory.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record StrokeUpdateRequest( + @Schema(description = "영법 Id", example = "1") Long id, + @Schema(description = "영법 이름", example = "자유형") String name, + @Schema(description = "바퀴 수", example = "3") Short laps, + @Schema(description = "미터", example = "150") Integer meter) {} diff --git a/module-presentation/src/main/java/com/depromeet/memory/dto/response/MemoryResponse.java b/module-presentation/src/main/java/com/depromeet/memory/dto/response/MemoryResponse.java index c31180b5..b2ed0178 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/dto/response/MemoryResponse.java +++ b/module-presentation/src/main/java/com/depromeet/memory/dto/response/MemoryResponse.java @@ -44,28 +44,10 @@ public MemoryResponse( LocalTime endTime, Short lane, String diary) { - List resultStrokes = - strokes.stream() - .map( - stroke -> { - if (stroke.getLaps() != null) { - return Stroke.builder() - .id(stroke.getId()) - .name(stroke.getName()) - .laps(stroke.getLaps()) - .meter(stroke.getLaps() * lane) - .build(); - } else { - return Stroke.builder() - .id(stroke.getId()) - .name(stroke.getName()) - .laps((short) (stroke.getMeter() / lane)) - .meter(stroke.getMeter()) - .build(); - } - }) - .toList(); + // 영법 별 바퀴 수와 미터 수를 계산 + List resultStrokes = getResultStrokes(strokes, lane); + // 기록 전체 바퀴 수와 미터 수를 계산 Integer totalLap = 0; Integer totalMeter = 0; for (Stroke stroke : resultStrokes) { @@ -78,32 +60,52 @@ public MemoryResponse( this.pool = pool; this.memoryDetail = memoryDetail; this.strokes = resultStrokes; - this.images = - images.stream() - .map( - image -> - Image.builder() - .id(image.getId()) - .originImageName(image.getOriginImageName()) - .imageName(image.getImageName()) - .imageUrl(image.getImageUrl()) - .build()) - .toList(); + this.images = getImageSource(images); // 순환참조 방지를 위해 Memory 필드 제외 this.recordAt = recordAt; this.startTime = startTime; this.endTime = endTime; - this.duration = - LocalTime.of( - endTime.minusHours(startTime.getHour()).getHour(), - endTime.minusMinutes(startTime.getMinute()).getMinute(), - 0); + this.duration = getDuration(startTime, endTime); // 수영을 한 시간 계산 this.lane = lane; this.totalLap = totalLap; this.totalMeter = totalMeter; this.diary = diary; } - public static MemoryResponse from(Memory memory) { + private static LocalTime getDuration(LocalTime startTime, LocalTime endTime) { + return LocalTime.of( + endTime.minusHours(startTime.getHour()).getHour(), + endTime.minusMinutes(startTime.getMinute()).getMinute(), + 0); + } + + private static List getImageSource(List images) { + return images.stream().map(Image::withoutMemory).toList(); + } + + private static List getResultStrokes(List strokes, Short lane) { + return strokes.stream() + .map( + stroke -> { + if (stroke.getLaps() != null) { + return Stroke.builder() + .id(stroke.getId()) + .name(stroke.getName()) + .laps(stroke.getLaps()) + .meter(stroke.getLaps() * lane) + .build(); + } else { + return Stroke.builder() + .id(stroke.getId()) + .name(stroke.getName()) + .laps((short) (stroke.getMeter() / lane)) + .meter(stroke.getMeter()) + .build(); + } + }) + .toList(); + } + + public static MemoryResponse of(Memory memory) { MemberSimpleResponse memberSimple = new MemberSimpleResponse( memory.getMember().getGoal(), memory.getMember().getName()); diff --git a/module-presentation/src/main/java/com/depromeet/memory/facade/MemoryFacade.java b/module-presentation/src/main/java/com/depromeet/memory/facade/MemoryFacade.java new file mode 100644 index 00000000..18d0d394 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/memory/facade/MemoryFacade.java @@ -0,0 +1,62 @@ +package com.depromeet.memory.facade; + +import com.depromeet.dto.response.CustomSliceResponse; +import com.depromeet.image.service.ImageUploadService; +import com.depromeet.member.Member; +import com.depromeet.member.service.MemberService; +import com.depromeet.memory.Memory; +import com.depromeet.memory.Stroke; +import com.depromeet.memory.dto.request.MemoryCreateRequest; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; +import com.depromeet.memory.dto.response.MemoryResponse; +import com.depromeet.memory.service.MemoryService; +import com.depromeet.memory.service.StrokeService; +import com.depromeet.memory.service.TimelineService; +import com.depromeet.pool.service.PoolService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemoryFacade { + private final MemberService memberService; + private final MemoryService memoryService; + private final StrokeService strokeService; + private final ImageUploadService imageUploadService; + private final TimelineService timelineService; + private final PoolService poolService; + + @Transactional + public void create(Long memberId, MemoryCreateRequest request) { + Member writer = memberService.findById(memberId); + Memory newMemory = memoryService.save(writer, request); + List strokes = strokeService.saveAll(newMemory, request.getStrokes()); + imageUploadService.addMemoryIdToImages(newMemory, request.getImageIdList()); + poolService.createSearchLog(writer, request.getPoolId()); + } + + @Transactional + public MemoryResponse update(Long memoryId, MemoryUpdateRequest request) { + Memory memory = memoryService.findById(memoryId); + List stokes = strokeService.updateAll(memory, request.getStrokes()); + return MemoryResponse.of(memoryService.update(memoryId, request, stokes)); + } + + public MemoryResponse findById(Long memoryId) { + return MemoryResponse.of(memoryService.findById(memoryId)); + } + + public CustomSliceResponse getTimelineByMemberIdAndCursorAndDate( + Long memberId, + Long cursorId, + String cursorRecordAt, + String date, + boolean showNewer, + int size) { + return timelineService.getTimelineByMemberIdAndCursorAndDate( + memberId, cursorId, cursorRecordAt, date, showNewer, size); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/MemoryService.java b/module-presentation/src/main/java/com/depromeet/memory/service/MemoryService.java index f00320d2..d1a128a0 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/MemoryService.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/MemoryService.java @@ -1,11 +1,16 @@ package com.depromeet.memory.service; +import com.depromeet.member.Member; import com.depromeet.memory.Memory; +import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.MemoryCreateRequest; -import com.depromeet.memory.dto.response.MemoryResponse; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; +import java.util.List; public interface MemoryService { - Memory save(MemoryCreateRequest memoryCreateRequest); + Memory save(Member member, MemoryCreateRequest request); - MemoryResponse findById(Long memoryId); + Memory findById(Long memoryId); + + Memory update(Long memoryId, MemoryUpdateRequest memoryUpdateRequest, List strokes); } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/MemoryServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/MemoryServiceImpl.java index d677cf8c..dce0e45e 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/MemoryServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/MemoryServiceImpl.java @@ -1,22 +1,24 @@ package com.depromeet.memory.service; +import com.depromeet.exception.ForbiddenException; import com.depromeet.exception.InternalServerException; import com.depromeet.exception.NotFoundException; -import com.depromeet.exception.UnauthorizedException; import com.depromeet.member.Member; import com.depromeet.member.repository.MemberRepository; import com.depromeet.memory.Memory; import com.depromeet.memory.MemoryDetail; +import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.MemoryCreateRequest; -import com.depromeet.memory.dto.response.MemoryResponse; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; import com.depromeet.memory.repository.MemoryDetailRepository; import com.depromeet.memory.repository.MemoryRepository; import com.depromeet.pool.Pool; import com.depromeet.pool.repository.PoolRepository; import com.depromeet.security.AuthorizationUtil; -import com.depromeet.type.member.MemberErrorType; +import com.depromeet.type.memory.MemoryDetailErrorType; import com.depromeet.type.memory.MemoryErrorType; import com.depromeet.type.pool.PoolErrorType; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -33,33 +35,22 @@ public class MemoryServiceImpl implements MemoryService { private final PoolRepository poolRepository; @Transactional - public Memory save(MemoryCreateRequest memoryCreateRequest) { - Long loginId = authorizationUtil.getLoginId(); - Member writer = - memberRepository - .findById(loginId) - .orElseThrow(() -> new UnauthorizedException(MemberErrorType.NOT_FOUND)); - MemoryDetail memoryDetail = getMemoryDetail(memoryCreateRequest); + public Memory save(Member writer, MemoryCreateRequest request) { + MemoryDetail memoryDetail = getMemoryDetail(request); if (memoryDetail != null) { memoryDetail = memoryDetailRepository.save(memoryDetail); } - Pool pool = null; - if (memoryCreateRequest.getPoolId() != null) { - pool = - poolRepository - .findById(memoryCreateRequest.getPoolId()) - .orElseThrow(() -> new NotFoundException(PoolErrorType.NOT_FOUND)); - } + Pool pool = poolRepository.findById(request.getPoolId()).orElse(null); Memory memory = Memory.builder() .member(writer) .pool(pool) .memoryDetail(memoryDetail) - .recordAt(memoryCreateRequest.getRecordAt()) - .startTime(memoryCreateRequest.getStartTime()) - .endTime(memoryCreateRequest.getEndTime()) - .lane(memoryCreateRequest.getLane()) - .diary(memoryCreateRequest.getDiary()) + .recordAt(request.getRecordAt()) + .startTime(request.getStartTime()) + .endTime(request.getEndTime()) + .lane(request.getLane()) + .diary(request.getDiary()) .build(); if (memory == null) { throw new InternalServerException(MemoryErrorType.CREATE_FAILED); @@ -68,12 +59,72 @@ public Memory save(MemoryCreateRequest memoryCreateRequest) { } @Override - public MemoryResponse findById(Long memoryId) { - Memory memory = - memoryRepository - .findById(memoryId) - .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); - return MemoryResponse.from(memory); + public Memory findById(Long memoryId) { + return memoryRepository + .findById(memoryId) + .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); + } + + @Override + @Transactional + public Memory update( + Long memoryId, MemoryUpdateRequest memoryUpdateRequest, List strokes) { + Memory memory = findById(memoryId); + + validateMemoryMemberMismatch(memory); + + // MemoryDetail 수정 + MemoryDetail updateMemoryDetail = + MemoryDetail.builder() + .item(memoryUpdateRequest.getItem()) + .heartRate(memoryUpdateRequest.getHeartRate()) + .pace(memoryUpdateRequest.getPace()) + .kcal(memoryUpdateRequest.getKcal()) + .build(); + if (memory.getMemoryDetail() != null) { + Long memoryDetailId = memory.getMemoryDetail().getId(); + updateMemoryDetail = + memoryDetailRepository + .update(memoryDetailId, updateMemoryDetail) + .orElseThrow( + () -> new NotFoundException(MemoryDetailErrorType.NOT_FOUND)); + } else { + updateMemoryDetail = memoryDetailRepository.save(updateMemoryDetail); + } + + // Pool 정보 찾기 + Pool updatePool = memory.getPool(); + if (memoryUpdateRequest.getPoolId() != null) { + updatePool = + poolRepository + .findById(memoryUpdateRequest.getPoolId()) + .orElseThrow(() -> new NotFoundException(PoolErrorType.NOT_FOUND)); + } + + // Memory 수정 + Memory updateMemory = + Memory.builder() + .member(memory.getMember()) + .pool(updatePool) + .memoryDetail(updateMemoryDetail) + .strokes(strokes) + .recordAt(memoryUpdateRequest.getRecordAt()) + .startTime(memoryUpdateRequest.getStartTime()) + .endTime(memoryUpdateRequest.getEndTime()) + .lane(memoryUpdateRequest.getLane()) + .diary(memoryUpdateRequest.getDiary()) + .build(); + + return memoryRepository + .update(memoryId, updateMemory) + .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); + } + + private void validateMemoryMemberMismatch(Memory memory) { + Long loginId = authorizationUtil.getLoginId(); + if (!memory.getMember().getId().equals(loginId)) { + throw new ForbiddenException(MemoryErrorType.UPDATE_FAILED); + } } private MemoryDetail getMemoryDetail(MemoryCreateRequest memoryCreateRequest) { diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/StrokeService.java b/module-presentation/src/main/java/com/depromeet/memory/service/StrokeService.java index 86c5cef1..961d3de7 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/StrokeService.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/StrokeService.java @@ -3,6 +3,7 @@ import com.depromeet.memory.Memory; import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.StrokeCreateRequest; +import com.depromeet.memory.dto.request.StrokeUpdateRequest; import java.util.List; public interface StrokeService { @@ -11,4 +12,6 @@ public interface StrokeService { List saveAll(Memory memory, List strokes); List getAllByMemoryId(Long memoryId); + + List updateAll(Memory memory, List strokes); } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/StrokeServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/StrokeServiceImpl.java index 61a2c0ce..5040e163 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/StrokeServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/StrokeServiceImpl.java @@ -3,9 +3,11 @@ import com.depromeet.memory.Memory; import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.StrokeCreateRequest; +import com.depromeet.memory.dto.request.StrokeUpdateRequest; import com.depromeet.memory.repository.StrokeRepository; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Predicate; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -42,4 +44,73 @@ public List saveAll(Memory memory, List strokes) { public List getAllByMemoryId(Long memoryId) { return strokeRepository.findAllByMemoryId(memoryId); } + + @Override + public List updateAll(Memory memory, List strokes) { + List beforeStrokes = memory.getStrokes(); + + if ((beforeStrokes == null || beforeStrokes.isEmpty()) + && (strokes == null || strokes.isEmpty())) { + return null; + } + + if (beforeStrokes == null || beforeStrokes.isEmpty()) { + List result = new CopyOnWriteArrayList<>(); + strokes.forEach( + stroke -> { + Stroke updateStroke = + Stroke.builder() + .memory(memory) + .name(stroke.name()) + .laps(stroke.laps()) + .meter(stroke.meter()) + .build(); + result.add(updateStroke); + strokeRepository.save(updateStroke); + }); + return result; + } + + if (strokes == null || strokes.isEmpty()) { + beforeStrokes.forEach(stroke -> strokeRepository.deleteById(stroke.getId())); + return null; + } + + List originStrokeIds = beforeStrokes.stream().map(Stroke::getId).toList(); + List updateStrokeIds = strokes.stream().map(StrokeUpdateRequest::id).toList(); + List existStrokeIds = + originStrokeIds.stream() + .filter(id -> updateStrokeIds.stream().anyMatch(Predicate.isEqual(id))) + .toList(); + beforeStrokes.forEach( + stroke -> { + if (!existStrokeIds.contains(stroke.getId())) { + strokeRepository.deleteById(stroke.getId()); + } + }); + strokes.forEach( + stroke -> { + if (stroke.id() != null) { + Stroke updateStroke = + Stroke.builder() + .id(stroke.id()) + .memory(memory) + .name(stroke.name()) + .laps(stroke.laps()) + .meter(stroke.meter()) + .build(); + strokeRepository.save(updateStroke); + } else { + Stroke updateStroke = + Stroke.builder() + .memory(memory) + .name(stroke.name()) + .laps(stroke.laps()) + .meter(stroke.meter()) + .build(); + strokeRepository.save(updateStroke); + } + }); + return getAllByMemoryId(memory.getId()); + } } diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java index adcae799..98160d17 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java @@ -24,8 +24,8 @@ @Slf4j @Service -@Transactional @RequiredArgsConstructor +@Transactional(readOnly = true) public class TimelineServiceImpl implements TimelineService { private final MemoryRepository memoryRepository; diff --git a/module-presentation/src/main/java/com/depromeet/pool/api/PoolApi.java b/module-presentation/src/main/java/com/depromeet/pool/api/PoolApi.java index 545066aa..3c9661f0 100644 --- a/module-presentation/src/main/java/com/depromeet/pool/api/PoolApi.java +++ b/module-presentation/src/main/java/com/depromeet/pool/api/PoolApi.java @@ -1,13 +1,21 @@ package com.depromeet.pool.api; import com.depromeet.dto.response.ApiResponse; -import com.depromeet.pool.dto.response.PoolResponseDto; +import com.depromeet.pool.dto.response.PoolInitialResponse; +import com.depromeet.pool.dto.response.PoolSearchResponse; +import com.depromeet.security.LoginMember; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.RequestParam; @Tag(name = "수영장(Pool)") public interface PoolApi { @Operation(summary = "수영장 검색") - ApiResponse searchPoolsByName(@RequestParam(required = false) String query); + ApiResponse searchPoolsByNameQuery( + @Schema(description = "수영장 검색 입력값", example = "강남") @RequestParam(value = "nameQuery") + String nameQuery); + + @Operation(summary = "즐겨찾기 및 최근 검색 수영장 조회") + ApiResponse getFavoriteAndSearchedPools(@LoginMember Long memberId); } diff --git a/module-presentation/src/main/java/com/depromeet/pool/api/PoolController.java b/module-presentation/src/main/java/com/depromeet/pool/api/PoolController.java index cb38e232..a9be94cc 100644 --- a/module-presentation/src/main/java/com/depromeet/pool/api/PoolController.java +++ b/module-presentation/src/main/java/com/depromeet/pool/api/PoolController.java @@ -1,11 +1,19 @@ package com.depromeet.pool.api; import com.depromeet.dto.response.ApiResponse; -import com.depromeet.pool.dto.response.PoolResponseDto; +import com.depromeet.pool.dto.request.FavoritePoolCreateRequest; +import com.depromeet.pool.dto.response.PoolInitialResponse; +import com.depromeet.pool.dto.response.PoolSearchResponse; import com.depromeet.pool.service.PoolService; +import com.depromeet.security.LoginMember; import com.depromeet.type.pool.PoolSuccessType; +import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -17,9 +25,28 @@ public class PoolController implements PoolApi { private final PoolService poolService; @GetMapping("/search") - public ApiResponse searchPoolsByName( - @RequestParam(required = false) String nameQuery) { + public ApiResponse searchPoolsByNameQuery( + @RequestParam(value = "nameQuery") String nameQuery) { return ApiResponse.success( PoolSuccessType.SEARCH_SUCCESS, poolService.findPoolsByName(nameQuery)); } + + @GetMapping("/search/initial") + public ApiResponse getFavoriteAndSearchedPools( + @LoginMember Long memberId) { + return ApiResponse.success( + PoolSuccessType.INITIAL_GET_SUCCESS, + poolService.getFavoriteAndSearchedPools(memberId)); + } + + @PutMapping("/favorite") + public ResponseEntity createFavoritePool( + @LoginMember Long memberId, @Valid @RequestBody FavoritePoolCreateRequest request) { + String uri = poolService.putFavoritePool(memberId, request); + + if (uri == null) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.created(URI.create(uri)).build(); + } } diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/request/FavoritePoolCreateRequest.java b/module-presentation/src/main/java/com/depromeet/pool/dto/request/FavoritePoolCreateRequest.java new file mode 100644 index 00000000..a528e720 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/dto/request/FavoritePoolCreateRequest.java @@ -0,0 +1,7 @@ +package com.depromeet.pool.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +public record FavoritePoolCreateRequest( + @Schema(description = "수영장 ID", example = "77") @NotNull Long poolId) {} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoDto.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoDto.java deleted file mode 100644 index 607ff197..00000000 --- a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.depromeet.pool.dto.response; - -import com.depromeet.pool.Pool; - -public record PoolInfoDto(Long poolId, String name) { - public static PoolInfoDto of(Pool pool) { - return new PoolInfoDto(pool.getId(), pool.getName()); - } -} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoResponse.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoResponse.java new file mode 100644 index 00000000..326f9825 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInfoResponse.java @@ -0,0 +1,21 @@ +package com.depromeet.pool.dto.response; + +import com.depromeet.pool.FavoritePool; +import com.depromeet.pool.Pool; +import com.depromeet.pool.PoolSearch; + +public record PoolInfoResponse(Long poolId, String name, String address) { + public static PoolInfoResponse of(Pool pool) { + return new PoolInfoResponse(pool.getId(), pool.getName(), pool.getAddress()); + } + + public static PoolInfoResponse of(FavoritePool favoritePool) { + Pool pool = favoritePool.getPool(); + return new PoolInfoResponse(pool.getId(), pool.getName(), pool.getAddress()); + } + + public static PoolInfoResponse of(PoolSearch poolSearch) { + Pool pool = poolSearch.getPool(); + return new PoolInfoResponse(pool.getId(), pool.getName(), pool.getAddress()); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInitialResponse.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInitialResponse.java new file mode 100644 index 00000000..44b75609 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolInitialResponse.java @@ -0,0 +1,35 @@ +package com.depromeet.pool.dto.response; + +import com.depromeet.pool.FavoritePool; +import com.depromeet.pool.PoolSearch; +import java.util.ArrayList; +import java.util.List; + +public record PoolInitialResponse( + List favoritePools, List searchedPools) { + public static PoolInitialResponse of( + List favoritePools, List searchedPools) { + List favoritePoolIds = + favoritePools.stream().map(it -> it.getPool().getId()).toList(); + return new PoolInitialResponse( + getFavoritePoolsInfo(favoritePools), + getSearchedPoolsInfo(favoritePoolIds, searchedPools)); + } + + private static List getFavoritePoolsInfo(List favoritePools) { + return favoritePools.stream().map(PoolInfoResponse::of).toList(); + } + + private static List getSearchedPoolsInfo( + List favoritePoolIds, List poolSearches) { + List searchedPoolsList = new ArrayList<>(); + for (PoolSearch search : poolSearches) { + if (favoritePoolIds.contains(search.getPool().getId())) { + searchedPoolsList.add(PoolSearchInfoResponse.of(search, true)); + } else { + searchedPoolsList.add(PoolSearchInfoResponse.of(search, false)); + } + } + return searchedPoolsList; + } +} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolResponseDto.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolResponseDto.java deleted file mode 100644 index 75e06a3d..00000000 --- a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolResponseDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.depromeet.pool.dto.response; - -import com.depromeet.pool.Pool; -import java.util.List; - -public record PoolResponseDto(List poolInfos) { - public static PoolResponseDto of(List pools) { - if (pools == null) { - return new PoolResponseDto(null); - } - return new PoolResponseDto(pools.stream().map(PoolInfoDto::of).toList()); - } -} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchInfoResponse.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchInfoResponse.java new file mode 100644 index 00000000..29b2059c --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchInfoResponse.java @@ -0,0 +1,12 @@ +package com.depromeet.pool.dto.response; + +import com.depromeet.pool.Pool; +import com.depromeet.pool.PoolSearch; + +public record PoolSearchInfoResponse(Long poolId, String name, String address, boolean isFavorite) { + public static PoolSearchInfoResponse of(PoolSearch poolSearch, boolean isFavorite) { + Pool pool = poolSearch.getPool(); + return new PoolSearchInfoResponse( + pool.getId(), pool.getName(), pool.getAddress(), isFavorite); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchResponse.java b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchResponse.java new file mode 100644 index 00000000..a80dcc81 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/dto/response/PoolSearchResponse.java @@ -0,0 +1,13 @@ +package com.depromeet.pool.dto.response; + +import com.depromeet.pool.Pool; +import java.util.List; + +public record PoolSearchResponse(List poolInfos) { + public static PoolSearchResponse of(List pools) { + if (pools == null) { + return new PoolSearchResponse(null); + } + return new PoolSearchResponse(pools.stream().map(PoolInfoResponse::of).toList()); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/pool/service/PoolService.java b/module-presentation/src/main/java/com/depromeet/pool/service/PoolService.java index f3d936d2..c98a6a4d 100644 --- a/module-presentation/src/main/java/com/depromeet/pool/service/PoolService.java +++ b/module-presentation/src/main/java/com/depromeet/pool/service/PoolService.java @@ -1,7 +1,16 @@ package com.depromeet.pool.service; -import com.depromeet.pool.dto.response.PoolResponseDto; +import com.depromeet.member.Member; +import com.depromeet.pool.dto.request.FavoritePoolCreateRequest; +import com.depromeet.pool.dto.response.PoolInitialResponse; +import com.depromeet.pool.dto.response.PoolSearchResponse; public interface PoolService { - PoolResponseDto findPoolsByName(String nameQuery); + PoolSearchResponse findPoolsByName(String nameQuery); + + PoolInitialResponse getFavoriteAndSearchedPools(Long memberId); + + String putFavoritePool(Long memberId, FavoritePoolCreateRequest request); + + String createSearchLog(Member member, Long poolId); } diff --git a/module-presentation/src/main/java/com/depromeet/pool/service/PoolServiceImpl.java b/module-presentation/src/main/java/com/depromeet/pool/service/PoolServiceImpl.java index 37143bf8..3d4fbd29 100644 --- a/module-presentation/src/main/java/com/depromeet/pool/service/PoolServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/pool/service/PoolServiceImpl.java @@ -1,22 +1,95 @@ package com.depromeet.pool.service; +import static com.depromeet.pool.service.PoolValidator.*; + +import com.depromeet.exception.NotFoundException; +import com.depromeet.member.Member; +import com.depromeet.member.repository.MemberRepository; +import com.depromeet.pool.FavoritePool; import com.depromeet.pool.Pool; -import com.depromeet.pool.dto.response.PoolResponseDto; +import com.depromeet.pool.PoolSearch; +import com.depromeet.pool.dto.request.FavoritePoolCreateRequest; +import com.depromeet.pool.dto.response.PoolInitialResponse; +import com.depromeet.pool.dto.response.PoolSearchResponse; import com.depromeet.pool.repository.PoolRepository; +import com.depromeet.type.member.MemberErrorType; +import com.depromeet.type.pool.PoolErrorType; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class PoolServiceImpl implements PoolService { private final PoolRepository poolRepository; + private final MemberRepository memberRepository; @Override - public PoolResponseDto findPoolsByName(String nameQuery) { + public PoolSearchResponse findPoolsByName(String nameQuery) { List findPools = poolRepository.findPoolsByName(nameQuery); - return PoolResponseDto.of(findPools); + return PoolSearchResponse.of(findPools); + } + + @Override + public PoolInitialResponse getFavoriteAndSearchedPools(Long memberId) { + List favoritePools = poolRepository.findFavoritePools(memberId); + List searchedPools = poolRepository.findSearchedPools(memberId); + return PoolInitialResponse.of(favoritePools, searchedPools); + } + + @Override + @Transactional + public String putFavoritePool(Long memberId, FavoritePoolCreateRequest request) { + Member member = getMember(memberId); + Pool pool = getPool(request.poolId()); + FavoritePool favoritePool = createFavoritePool(member, pool); + + // 여기서 삭제 로직 + if (poolRepository.existsFavoritePool(favoritePool)) { + poolRepository.deleteFavoritePool(favoritePool); + return null; + } + return poolRepository.saveFavoritePool(favoritePool).getId().toString(); + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public String createSearchLog(Member member, Long poolId) { + Pool pool = getPool(poolId); + PoolSearch poolSearch = createPoolSearch(member, pool); + PoolSearch savedPoolSearch = poolRepository.savePoolSearch(poolSearch); + + return savedPoolSearch.getId().toString(); + } + + private FavoritePool createFavoritePool(Member member, Pool pool) { + return FavoritePool.builder().member(member).pool(pool).build(); + } + + private PoolSearch createPoolSearch(Member member, Pool pool) { + return PoolSearch.builder().member(member).pool(pool).build(); + } + + private Member getMember(Long memberId) { + return memberRepository + .findById(memberId) + .orElseThrow(() -> new NotFoundException(MemberErrorType.NOT_FOUND)); + } + + private Pool getPool(Long poolId) { + return poolRepository + .findById(poolId) + .orElseThrow(() -> new NotFoundException(PoolErrorType.NOT_FOUND)); + } + + private FavoritePool getFavoritePool(Long favoritePoolId) { + return poolRepository + .findFavoritePoolById(favoritePoolId) + .orElseThrow(() -> new NotFoundException(PoolErrorType.FAVORITE_NOT_FOUND)); } } diff --git a/module-presentation/src/main/java/com/depromeet/pool/service/PoolValidator.java b/module-presentation/src/main/java/com/depromeet/pool/service/PoolValidator.java new file mode 100644 index 00000000..9d1fbbcf --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/pool/service/PoolValidator.java @@ -0,0 +1,13 @@ +package com.depromeet.pool.service; + +import com.depromeet.exception.ForbiddenException; +import com.depromeet.type.pool.PoolErrorType; + +public class PoolValidator { + public static boolean validateOwnerOfFavorite(Long memberId, Long favoritePoolMemberId) { + if (!memberId.equals(favoritePoolMemberId)) { + throw new ForbiddenException(PoolErrorType.FAVORITE_FORBIDDEN); + } + return true; + } +} diff --git a/module-presentation/src/main/resources/application-local.yml b/module-presentation/src/main/resources/application-local.yml index 8b7b7b26..5351d763 100644 --- a/module-presentation/src/main/resources/application-local.yml +++ b/module-presentation/src/main/resources/application-local.yml @@ -5,7 +5,7 @@ spring: import: optional:application-secret.properties jpa: hibernate: - ddl-auto: create # 실제 서버에서 사용시 모든 데이터가 다 날아가므로 주의 + ddl-auto: update # 실제 서버에서 사용시 모든 데이터가 다 날아가므로 주의 defer-datasource-initialization: false properties: hibernate: diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryDetailRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryDetailRepository.java index b3a91650..034733ad 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryDetailRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryDetailRepository.java @@ -4,6 +4,7 @@ import com.depromeet.memory.repository.MemoryDetailRepository; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class FakeMemoryDetailRepository implements MemoryDetailRepository { private Long autoGeneratedId = 0L; @@ -28,4 +29,34 @@ public MemoryDetail save(MemoryDetail memoryDetail) { return memoryDetail; } } + + @Override + public Optional update(Long id, MemoryDetail updateMemoryDetail) { + Optional md = data.stream().filter(item -> item.getId().equals(id)).findAny(); + if (md.isEmpty()) { + return Optional.empty(); + } else { + MemoryDetail origin = md.get(); + return Optional.of( + MemoryDetail.builder() + .id(id) + .item( + updateMemoryDetail.getItem() != null + ? updateMemoryDetail.getItem() + : origin.getItem()) + .heartRate( + updateMemoryDetail.getHeartRate() != null + ? updateMemoryDetail.getHeartRate() + : origin.getHeartRate()) + .pace( + updateMemoryDetail.getPace() != null + ? updateMemoryDetail.getPace() + : origin.getPace()) + .kcal( + updateMemoryDetail.getKcal() != null + ? updateMemoryDetail.getKcal() + : origin.getKcal()) + .build()); + } + } } diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java index 4d4db6b5..a50fd19b 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java @@ -42,6 +42,57 @@ public Optional findById(Long memoryId) { return data.stream().filter(item -> item.getId().equals(memoryId)).findAny(); } + @Override + public Optional update(Long memoryId, Memory memoryUpdate) { + Optional md = data.stream().filter(item -> item.getId().equals(memoryId)).findAny(); + if (md.isEmpty()) { + return Optional.empty(); + } else { + Memory origin = md.get(); + return Optional.of( + Memory.builder() + .id(memoryId) + .member(origin.getMember()) + .pool( + memoryUpdate.getPool() != null + ? memoryUpdate.getPool() + : origin.getPool()) + .memoryDetail( + memoryUpdate.getMemoryDetail() != null + ? memoryUpdate.getMemoryDetail() + : origin.getMemoryDetail()) + .strokes( + memoryUpdate.getStrokes() != null + ? memoryUpdate.getStrokes() + : origin.getStrokes()) + .images( + memoryUpdate.getImages() != null + ? memoryUpdate.getImages() + : origin.getImages()) + .recordAt( + memoryUpdate.getRecordAt() != null + ? memoryUpdate.getRecordAt() + : origin.getRecordAt()) + .startTime( + memoryUpdate.getStartTime() != null + ? memoryUpdate.getStartTime() + : origin.getStartTime()) + .endTime( + memoryUpdate.getEndTime() != null + ? memoryUpdate.getEndTime() + : origin.getEndTime()) + .lane( + memoryUpdate.getLane() != null + ? memoryUpdate.getLane() + : origin.getLane()) + .diary( + memoryUpdate.getDiary() != null + ? memoryUpdate.getDiary() + : origin.getDiary()) + .build()); + } + } + @Override public Slice findPrevMemoryByMemberId( Long memberId, diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakePoolRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakePoolRepository.java index c25cd11b..f76c5503 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakePoolRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakePoolRepository.java @@ -1,6 +1,8 @@ package com.depromeet.memory.mock; +import com.depromeet.pool.FavoritePool; import com.depromeet.pool.Pool; +import com.depromeet.pool.PoolSearch; import com.depromeet.pool.repository.PoolRepository; import java.util.ArrayList; import java.util.List; @@ -15,13 +17,46 @@ public List findPoolsByName(String nameQuery) { return data.stream().filter(item -> item.getName().contains(nameQuery)).limit(3).toList(); } + @Override + public List findFavoritePools(Long memberId) { + return null; + } + + @Override + public List findSearchedPools(Long memberId) { + return null; + } + @Override public Optional findById(Long poolId) { return data.stream().filter(item -> item.getId().equals(poolId)).findAny(); } + @Override + public Optional findFavoritePoolById(Long favoritePoolId) { + return Optional.empty(); + } + @Override public Pool save(Pool pool) { return null; } + + @Override + public PoolSearch savePoolSearch(PoolSearch poolSearch) { + return null; + } + + @Override + public FavoritePool saveFavoritePool(FavoritePool favoritePool) { + return null; + } + + @Override + public boolean existsFavoritePool(FavoritePool favoritePool) { + return false; + } + + @Override + public void deleteFavoritePool(FavoritePool favoritePool) {} } diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeStrokeRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeStrokeRepository.java index 3d235ce8..6110afaf 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeStrokeRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeStrokeRepository.java @@ -33,4 +33,9 @@ public Stroke save(Stroke stroke) { public List findAllByMemoryId(Long memoryId) { return data.stream().filter(item -> item.getMemory().getId().equals(memoryId)).toList(); } + + @Override + public void deleteById(Long id) { + data.removeIf(item -> item.getId().equals(id)); + } } diff --git a/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java b/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java index cef8d8f0..7ef91559 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java +++ b/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java @@ -3,15 +3,25 @@ import com.depromeet.member.Member; import com.depromeet.member.MemberRole; import com.depromeet.memory.Memory; +import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.MemoryCreateRequest; +import com.depromeet.memory.dto.request.MemoryUpdateRequest; +import com.depromeet.memory.dto.request.StrokeUpdateRequest; import com.depromeet.memory.mock.*; import java.time.LocalDate; import java.time.LocalTime; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class MemoryServiceTest { + // Stroke + private FakeStrokeRepository strokeRepository; + + private StrokeService strokeService; + + // Memory private FakeMemoryRepository memoryRepository; private FakeMemoryDetailRepository memoryDetailRepository; @@ -27,7 +37,13 @@ class MemoryServiceTest { @BeforeEach void init() { - // dependencies + // Dependencies + strokeRepository = new FakeStrokeRepository(); + + // Stroke service + strokeService = new StrokeServiceImpl(strokeRepository); + + // Dependencies memoryRepository = new FakeMemoryRepository(); memoryDetailRepository = new FakeMemoryDetailRepository(); @@ -36,7 +52,7 @@ void init() { poolRepository = new FakePoolRepository(); - // member create + // Member create member1 = Member.builder() .id(userId) @@ -46,7 +62,7 @@ void init() { .build(); memberRepository.save(member1); - // memoryService + // MemoryService memoryService = new MemoryServiceImpl( memoryRepository, @@ -74,4 +90,59 @@ void init() { Assertions.assertThat(memory.getStartTime()).isEqualTo(LocalTime.of(15, 0)); Assertions.assertThat(memory.getEndTime()).isEqualTo(LocalTime.of(15, 50)); } + + @Test + void 회원은_수영기록을_수정할_수_있다() { + // given + MemoryCreateRequest memoryCreateRequest = + MemoryCreateRequest.builder() + .recordAt(LocalDate.of(2024, 7, 15)) + .startTime(LocalTime.of(15, 0)) + .endTime(LocalTime.of(15, 50)) + .build(); + Memory memory = memoryService.save(memoryCreateRequest); + MemoryUpdateRequest memoryUpdateRequest = + MemoryUpdateRequest.builder() + .startTime(LocalTime.of(15, 30)) + .diary("Hello world~") + .build(); + + // when + Memory updateMemory = + memoryService.update(memory.getId(), memoryUpdateRequest, memory.getStrokes()); + + // then + Assertions.assertThat(updateMemory.getStartTime()).isEqualTo(LocalTime.of(15, 30)); + Assertions.assertThat(updateMemory.getDiary()).isEqualTo("Hello world~"); + Assertions.assertThat(updateMemory.getStrokes()).isEqualTo(null); + } + + @Test + void 회원은_영법을_수정할_수_있다() { + // given + StrokeUpdateRequest stroke1 = new StrokeUpdateRequest((Long) null, "자유형", (short) 4, null); + StrokeUpdateRequest stroke2 = new StrokeUpdateRequest((Long) null, "접영", (short) 2, null); + List strokes = List.of(stroke1, stroke2); + + MemoryCreateRequest memoryCreateRequest = + MemoryCreateRequest.builder() + .recordAt(LocalDate.of(2024, 7, 15)) + .startTime(LocalTime.of(15, 0)) + .endTime(LocalTime.of(15, 50)) + .build(); + Memory memory = memoryService.save(memoryCreateRequest); + MemoryUpdateRequest memoryUpdateRequest = + MemoryUpdateRequest.builder() + .startTime(LocalTime.of(15, 30)) + .diary("Hello world~") + .build(); + + // when + List updateStrokes = strokeService.updateAll(memory, strokes); + memory = memoryService.update(memory.getId(), memoryUpdateRequest, updateStrokes); + + // then + Assertions.assertThat(updateStrokes.size()).isEqualTo(2); + Assertions.assertThat(memory.getStrokes().size()).isEqualTo(2); + } } diff --git a/module-presentation/src/test/java/com/depromeet/memory/service/StrokeServiceTest.java b/module-presentation/src/test/java/com/depromeet/memory/service/StrokeServiceTest.java index 17b2fdf5..0ab11fac 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/service/StrokeServiceTest.java +++ b/module-presentation/src/test/java/com/depromeet/memory/service/StrokeServiceTest.java @@ -5,6 +5,7 @@ import com.depromeet.memory.Memory; import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.StrokeCreateRequest; +import com.depromeet.memory.mock.*; import com.depromeet.memory.mock.FakeMemoryRepository; import com.depromeet.memory.mock.FakeStrokeRepository; import java.time.LocalDate; @@ -27,7 +28,7 @@ class StrokeServiceTest { void init() { strokeRepository = new FakeStrokeRepository(); memoryRepository = new FakeMemoryRepository(); - strokeService = new StrokeServiceImpl(strokeRepository, memoryRepository); + strokeService = new StrokeServiceImpl(strokeRepository); } @Test From 874fbac33d3d6400c9b2f526a6efeae84d11afb5 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Sun, 21 Jul 2024 23:05:37 +0900 Subject: [PATCH 05/11] =?UTF-8?q?test:=20test=20=EC=88=98=EC=A0=95=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/service/MemoryServiceTest.java | 12 ++++++------ .../memory/service/TimelineServiceTest.java | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java b/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java index 7ef91559..ca844a06 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java +++ b/module-presentation/src/test/java/com/depromeet/memory/service/MemoryServiceTest.java @@ -33,7 +33,7 @@ class MemoryServiceTest { private MemoryService memoryService; private Long userId = 1L; // 로그인한 사용자 아이디 임의 지정 - private Member member1; + private Member member; @BeforeEach void init() { @@ -53,14 +53,14 @@ void init() { poolRepository = new FakePoolRepository(); // Member create - member1 = + member = Member.builder() .id(userId) .name("member1") .email("member1@gmail.com") .role(MemberRole.USER) .build(); - memberRepository.save(member1); + memberRepository.save(member); // MemoryService memoryService = @@ -83,7 +83,7 @@ void init() { .build(); // when - Memory memory = memoryService.save(memoryCreateRequest); + Memory memory = memoryService.save(member, memoryCreateRequest); // then Assertions.assertThat(memory.getRecordAt()).isEqualTo(LocalDate.of(2024, 7, 15)); @@ -100,7 +100,7 @@ void init() { .startTime(LocalTime.of(15, 0)) .endTime(LocalTime.of(15, 50)) .build(); - Memory memory = memoryService.save(memoryCreateRequest); + Memory memory = memoryService.save(member, memoryCreateRequest); MemoryUpdateRequest memoryUpdateRequest = MemoryUpdateRequest.builder() .startTime(LocalTime.of(15, 30)) @@ -130,7 +130,7 @@ void init() { .startTime(LocalTime.of(15, 0)) .endTime(LocalTime.of(15, 50)) .build(); - Memory memory = memoryService.save(memoryCreateRequest); + Memory memory = memoryService.save(member, memoryCreateRequest); MemoryUpdateRequest memoryUpdateRequest = MemoryUpdateRequest.builder() .startTime(LocalTime.of(15, 30)) diff --git a/module-presentation/src/test/java/com/depromeet/memory/service/TimelineServiceTest.java b/module-presentation/src/test/java/com/depromeet/memory/service/TimelineServiceTest.java index 9851b4a7..42a5b90c 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/service/TimelineServiceTest.java +++ b/module-presentation/src/test/java/com/depromeet/memory/service/TimelineServiceTest.java @@ -5,6 +5,7 @@ import com.depromeet.member.Member; import com.depromeet.member.MemberRole; import com.depromeet.memory.Memory; +import com.depromeet.memory.Stroke; import com.depromeet.memory.dto.request.MemoryCreateRequest; import com.depromeet.memory.dto.request.StrokeCreateRequest; import com.depromeet.memory.dto.response.TimelineResponseDto; @@ -86,33 +87,35 @@ Memory saveMemory() { .startTime(LocalTime.of(15, 0)) .endTime(LocalTime.of(15, 50)) .build(); - return memoryService.save(memoryCreateRequest); + return memoryService.save(member, memoryCreateRequest); } - void saveMeterStroke() { + List saveMeterStroke() { Integer meter = 50; List scr = new ArrayList<>(); for (int i = 0; i < STROKE_NAME_LIST.size(); i++) { scr.add(new StrokeCreateRequest(STROKE_NAME_LIST.get(i), null, meter)); expectedTotalMeter += meter; } - strokeService.saveAll(memory, scr); + return strokeService.saveAll(memory, scr); } - void saveLapsStroke() { + List saveLapsStroke() { List scr = new ArrayList<>(); Short laps = 2; for (int i = 0; i < STROKE_NAME_LIST.size(); i++) { scr.add(new StrokeCreateRequest(STROKE_NAME_LIST.get(i), laps, null)); expectedTotalMeter += laps * lane; } - strokeService.saveAll(memory, scr); + return strokeService.saveAll(memory, scr); } @Test void TimelineResponseDto로_변환_테스트_Stroke_meter로_저장() { // given - saveMeterStroke(); + List strokes = saveMeterStroke(); + memory.setStrokes(strokes); + memoryRepository.save(memory); // when TimelineResponseDto result = timelineService.mapToTimelineResponseDto(memory); @@ -124,7 +127,9 @@ void saveLapsStroke() { @Test void TimelineResponseDto로_변환_테스트_Stroke_laps로_저장() { // given - saveLapsStroke(); + List strokes = saveLapsStroke(); + memory.setStrokes(strokes); + memoryRepository.save(memory); // when TimelineResponseDto result = timelineService.mapToTimelineResponseDto(memory); From 3ade1111d50e43a93b49724b09b15e10c07d4dbc Mon Sep 17 00:00:00 2001 From: penrose15 Date: Wed, 24 Jul 2024 00:46:06 +0900 Subject: [PATCH 06/11] =?UTF-8?q?refactor:=20GlobalExceptionAdvice=20Field?= =?UTF-8?q?Error,=20ConstraintsViolation=20=EC=9D=91=EB=8B=B5=EA=B0=92=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/depromeet/common/ErrorResponse.java | 31 +++++++++++++++++++ .../common/GlobalExceptionAdvice.java | 15 ++------- .../common/dto/ConstraintViolationError.java | 23 ++++++++++++++ .../com/depromeet/common/dto/FieldError.java | 28 +++++++++++++++++ 4 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 module-presentation/src/main/java/com/depromeet/common/ErrorResponse.java create mode 100644 module-presentation/src/main/java/com/depromeet/common/dto/ConstraintViolationError.java create mode 100644 module-presentation/src/main/java/com/depromeet/common/dto/FieldError.java diff --git a/module-presentation/src/main/java/com/depromeet/common/ErrorResponse.java b/module-presentation/src/main/java/com/depromeet/common/ErrorResponse.java new file mode 100644 index 00000000..3e7616ee --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/common/ErrorResponse.java @@ -0,0 +1,31 @@ +package com.depromeet.common; + +import com.depromeet.common.dto.ConstraintViolationError; +import com.depromeet.common.dto.FieldError; +import com.fasterxml.jackson.annotation.JsonInclude; +import jakarta.validation.ConstraintViolation; +import java.util.List; +import java.util.Set; +import lombok.Getter; +import org.springframework.validation.BindingResult; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ErrorResponse { + private List fieldErrors; + private List constraintViolations; + + public ErrorResponse( + List fieldErrors, List constraintViolations) { + this.fieldErrors = fieldErrors; + this.constraintViolations = constraintViolations; + } + + public static ErrorResponse of(BindingResult bindingResult) { + return new ErrorResponse(FieldError.of(bindingResult), null); + } + + public static ErrorResponse of(Set> constraintViolations) { + return new ErrorResponse(null, ConstraintViolationError.of(constraintViolations)); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java b/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java index f0bbb174..dc0b7a3d 100644 --- a/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java +++ b/module-presentation/src/main/java/com/depromeet/common/GlobalExceptionAdvice.java @@ -8,14 +8,10 @@ import jakarta.validation.ConstraintViolationException; import jakarta.validation.UnexpectedTypeException; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.validation.Errors; -import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingRequestHeaderException; @@ -29,13 +25,7 @@ public class GlobalExceptionAdvice { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentNotValidException( final MethodArgumentNotValidException ex) { - Errors errors = ex.getBindingResult(); - Map validateDetails = new HashMap<>(); - - for (FieldError error : errors.getFieldErrors()) { - String validKeyName = String.format("valid_%s", error.getField()); - validateDetails.put(validKeyName, error.getDefaultMessage()); - } + ErrorResponse validateDetails = ErrorResponse.of(ex.getBindingResult()); return new ResponseEntity<>( ApiResponse.fail(CommonErrorType.REQUEST_VALIDATION, 400, validateDetails), HttpStatus.BAD_REQUEST); @@ -88,8 +78,9 @@ protected ResponseEntity> handlerConstraintDefinitionException( protected ResponseEntity> handlerConstraintViolationException( final ConstraintViolationException ex) { log.error(ex.getMessage()); + ErrorResponse constraintViolation = ErrorResponse.of(ex.getConstraintViolations()); return new ResponseEntity<>( - ApiResponse.fail(CommonErrorType.VALIDATION_FAILED, 400, ex.toString()), + ApiResponse.fail(CommonErrorType.VALIDATION_FAILED, 400, constraintViolation), HttpStatus.BAD_REQUEST); } diff --git a/module-presentation/src/main/java/com/depromeet/common/dto/ConstraintViolationError.java b/module-presentation/src/main/java/com/depromeet/common/dto/ConstraintViolationError.java new file mode 100644 index 00000000..f2a0a834 --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/common/dto/ConstraintViolationError.java @@ -0,0 +1,23 @@ +package com.depromeet.common.dto; + +import jakarta.validation.ConstraintViolation; +import java.util.List; +import java.util.Set; +import lombok.Builder; + +public record ConstraintViolationError(String propertyPath, Object rejectedValue, String reason) { + @Builder + public ConstraintViolationError {} + + public static List of(Set> violations) { + return violations.stream() + .map( + violation -> + ConstraintViolationError.builder() + .propertyPath(violation.getPropertyPath().toString()) + .rejectedValue(violation.getInvalidValue().toString()) + .reason(violation.getMessage()) + .build()) + .toList(); + } +} diff --git a/module-presentation/src/main/java/com/depromeet/common/dto/FieldError.java b/module-presentation/src/main/java/com/depromeet/common/dto/FieldError.java new file mode 100644 index 00000000..8b4c782e --- /dev/null +++ b/module-presentation/src/main/java/com/depromeet/common/dto/FieldError.java @@ -0,0 +1,28 @@ +package com.depromeet.common.dto; + +import java.util.List; +import lombok.Builder; +import org.springframework.validation.BindingResult; + +public record FieldError(String field, Object rejectedValue, String reason) { + @Builder + public FieldError {} + + public static List of(BindingResult bindingResult) { + List fieldErrors = + bindingResult.getFieldErrors(); + + return fieldErrors.stream() + .map( + error -> + FieldError.builder() + .field(String.format("valid_%s", error.getField())) + .rejectedValue( + error.getRejectedValue() != null + ? error.getRejectedValue().toString() + : "") + .reason(error.getDefaultMessage()) + .build()) + .toList(); + } +} From b9848f5c8c52d1caa56fcff99df61e61b3e2fd08 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Wed, 24 Jul 2024 22:21:20 +0900 Subject: [PATCH 07/11] =?UTF-8?q?refactor:=20Slice=20->=20Timeline?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20pre?= =?UTF-8?q?sentation=20module=20=EB=82=B4=20=EA=B0=95=EA=B2=B0=ED=95=A9=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20(#99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/depromeet/memory/Timeline.java | 24 ++++++++ .../dto/response/CustomSliceResponse.java | 2 +- module-presentation/build.gradle | 2 - .../memory/service/TimelineServiceImpl.java | 58 ++++++++++--------- .../memory/mock/FakeMemoryRepository.java | 11 ++-- 5 files changed, 62 insertions(+), 35 deletions(-) create mode 100644 module-domain/src/main/java/com/depromeet/memory/Timeline.java diff --git a/module-domain/src/main/java/com/depromeet/memory/Timeline.java b/module-domain/src/main/java/com/depromeet/memory/Timeline.java new file mode 100644 index 00000000..91de8cb9 --- /dev/null +++ b/module-domain/src/main/java/com/depromeet/memory/Timeline.java @@ -0,0 +1,24 @@ +package com.depromeet.memory; + +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class Timeline { + private List timelineContents; + private int pageSize; + private Long cursorId; + private boolean hasNext; + + @Builder + public Timeline(List timelineContents, int pageSize, Long cursorId, boolean hasNext) { + this.timelineContents = timelineContents != null ? timelineContents : new ArrayList<>(); + this.pageSize = pageSize != 0 ? pageSize : 10; + this.cursorId = cursorId; + this.hasNext = hasNext; + } +} diff --git a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java index ae8b2478..ff1a42de 100644 --- a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java +++ b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java @@ -2,7 +2,7 @@ import lombok.Builder; -public record CustomSliceResponse(T content, int pageNumber, int pageSize, boolean hasNext) { +public record CustomSliceResponse(T content, int pageSize, Long cursorId, boolean hasNext) { @Builder public CustomSliceResponse {} } diff --git a/module-presentation/build.gradle b/module-presentation/build.gradle index 300bea96..7c4a7c67 100644 --- a/module-presentation/build.gradle +++ b/module-presentation/build.gradle @@ -13,8 +13,6 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-validation' - //for test - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java index f5200a5f..37afd9b5 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java @@ -1,14 +1,17 @@ package com.depromeet.memory.service; import com.depromeet.dto.response.CustomSliceResponse; +import com.depromeet.exception.NotFoundException; import com.depromeet.image.Image; import com.depromeet.image.dto.response.MemoryImagesDto; import com.depromeet.memory.Memory; import com.depromeet.memory.Stroke; +import com.depromeet.memory.Timeline; import com.depromeet.memory.dto.request.TimelineRequestDto; import com.depromeet.memory.dto.response.StrokeResponse; import com.depromeet.memory.dto.response.TimelineResponseDto; import com.depromeet.memory.repository.MemoryRepository; +import com.depromeet.type.memory.MemoryErrorType; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; @@ -17,10 +20,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -34,28 +33,46 @@ public class TimelineServiceImpl implements TimelineService { @Override public CustomSliceResponse getTimelineByMemberIdAndCursorAndDate( Long memberId, TimelineRequestDto timeline) { - Slice memories = getTimelines(memberId, timeline); - Slice result = memories.map(this::mapToTimelineResponseDto); - return mapToCustomSliceResponse(result); + Timeline timelines = getTimelines(memberId, timeline); + + return mapToCustomSliceResponse(timelines); } - private Slice getTimelines(Long memberId, TimelineRequestDto timeline) { - Pageable pageable = - PageRequest.of(0, timeline.getSize(), Sort.by(Sort.Order.desc("recordAt"))); + private Timeline getTimelines(Long memberId, TimelineRequestDto timeline) { + LocalDate cursorRecordAt = null; + if (timeline.getCursorId() != null) { + Memory memory = + memoryRepository + .findById(timeline.getCursorId()) + .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); + cursorRecordAt = memory.getRecordAt(); + } LocalDate parsedDate = getLocalDateOrNull(timeline.getDate()); + if (timeline.isShowNewer()) { - return memoryRepository.findNextMemoryByMemberId( - memberId, timeline.getCursorId(), pageable, parsedDate); + return memoryRepository.findNextMemoryByMemberId(memberId, cursorRecordAt, parsedDate); } else { - return memoryRepository.findPrevMemoryByMemberId( - memberId, timeline.getCursorId(), pageable, parsedDate); + return memoryRepository.findPrevMemoryByMemberId(memberId, cursorRecordAt, parsedDate); } } + private CustomSliceResponse mapToCustomSliceResponse(Timeline timelines) { + List result = + timelines.getTimelineContents().stream() + .map(this::mapToTimelineResponseDto) + .toList(); + + return CustomSliceResponse.builder() + .content(result) + .pageSize(timelines.getPageSize()) + .cursorId(timelines.getCursorId()) + .hasNext(timelines.isHasNext()) + .build(); + } + private LocalDate getLocalDateOrNull(YearMonth date) { LocalDate lastDayOfMonth = null; - log.info("localDate : {}", date); if (date != null) { lastDayOfMonth = date.atEndOfMonth(); } @@ -169,15 +186,4 @@ private List imagesToDto(List images) { .build()) .toList(); } - - private CustomSliceResponse mapToCustomSliceResponse(Slice result) { - List content = result.getContent(); - - return CustomSliceResponse.builder() - .content(content) - .pageSize(result.getSize()) - .pageNumber(result.getNumber()) - .hasNext(result.hasNext()) - .build(); - } } diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java index 9210cd1d..1ff787fc 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java @@ -1,13 +1,12 @@ package com.depromeet.memory.mock; import com.depromeet.memory.Memory; +import com.depromeet.memory.Timeline; import com.depromeet.memory.repository.MemoryRepository; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; public class FakeMemoryRepository implements MemoryRepository { private Long autoGeneratedId = 0L; @@ -99,14 +98,14 @@ public Optional update(Long memoryId, Memory memoryUpdate) { } @Override - public Slice findPrevMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt) { + public Timeline findPrevMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { return null; } @Override - public Slice findNextMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt) { + public Timeline findNextMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { return null; } From 3a18915553983670f38436039c30621052365941 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Wed, 24 Jul 2024 22:31:30 +0900 Subject: [PATCH 08/11] =?UTF-8?q?fix:=20cursor=20=EA=B8=B0=EC=A4=80=20reco?= =?UTF-8?q?rdAt=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../memory/repository/MemoryRepository.java | 11 ++-- .../repository/MemoryRepositoryImpl.java | 59 ++++++++++++------- .../repository/MemoryRepositoryTest.java | 56 +++++++----------- .../memory/api/MemoryController.java | 2 +- .../dto/request/TimelineRequestDto.java | 7 --- 5 files changed, 65 insertions(+), 70 deletions(-) diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java index 274849be..211b8565 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java @@ -1,11 +1,10 @@ package com.depromeet.memory.repository; import com.depromeet.memory.Memory; +import com.depromeet.memory.Timeline; import java.time.LocalDate; import java.util.List; import java.util.Optional; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; public interface MemoryRepository { Memory save(Memory memory); @@ -16,11 +15,11 @@ public interface MemoryRepository { Optional update(Long memoryId, Memory memoryUpdate); - Slice findPrevMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt); + Timeline findPrevMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); - Slice findNextMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt); + Timeline findNextMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); List getCalendarByYearAndMonth(Long memberId, Integer year, Short month); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java index 344be10d..c1900ffc 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java @@ -7,6 +7,7 @@ import static com.depromeet.pool.entity.QPoolEntity.poolEntity; import com.depromeet.memory.Memory; +import com.depromeet.memory.Timeline; import com.depromeet.memory.entity.MemoryEntity; import com.depromeet.memory.entity.QMemoryEntity; import com.querydsl.core.types.dsl.BooleanExpression; @@ -18,9 +19,9 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.SliceImpl; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; @Slf4j @@ -78,59 +79,75 @@ public Optional update(Long memoryId, Memory memoryUpdate) { .map(entity -> entity.update(MemoryEntity.from(memoryUpdate)).toModel()); } - // ---- 날짜 선택 후 위아래 무한 스크롤 구현 - @Override - public Slice findPrevMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt) { + public Timeline findPrevMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { + Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "recordAt"); List result = queryFactory .selectFrom(memory) .where( memory.member.id.eq(memberId), - ltCursorId(cursorId), + ltCursorRecordAt(cursorRecordAt), loeRecordAt(recordAt)) .limit(pageable.getPageSize() + 1) .orderBy(memory.recordAt.desc()) .fetch(); List content = toModel(result); - boolean hasPrev = false; + boolean hasNext = false; + Long nextMemoryId = null; if (content.size() > pageable.getPageSize()) { content = new ArrayList<>(content); content.removeLast(); - hasPrev = true; + hasNext = true; + Memory lastMemory = content.getLast(); + nextMemoryId = lastMemory.getId(); } - return new SliceImpl<>(content, pageable, hasPrev); + return Timeline.builder() + .timelineContents(content) + .pageSize(10) + .cursorId(nextMemoryId) + .hasNext(hasNext) + .build(); } @Override - public Slice findNextMemoryByMemberId( - Long memberId, Long cursorId, Pageable pageable, LocalDate recordAt) { + public Timeline findNextMemoryByMemberId( + Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { + Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "recordAt"); + List result = queryFactory .selectFrom(memory) .where( memory.member.id.eq(memberId), - gtCursorId(cursorId), + gtCursorRecordAt(cursorRecordAt), goeRecordAt(recordAt)) .limit(pageable.getPageSize() + 1) .orderBy(memory.recordAt.asc()) .fetch(); - List content = toModel(result); boolean hasNext = false; + Long nextMemoryId = null; if (content.size() > pageable.getPageSize()) { content = new ArrayList<>(content); content.removeLast(); hasNext = true; + Memory lastMemory = content.getLast(); + nextMemoryId = lastMemory.getId(); } content = content.reversed(); - return new SliceImpl<>(content, pageable, hasNext); + return Timeline.builder() + .timelineContents(content) + .pageSize(10) + .cursorId(nextMemoryId) + .hasNext(hasNext) + .build(); } @Override @@ -161,18 +178,18 @@ private BooleanExpression loeRecordAt(LocalDate recordAt) { return memory.recordAt.loe(recordAt); } - private BooleanExpression ltCursorId(Long cursorId) { - if (cursorId == null) { + private BooleanExpression ltCursorRecordAt(LocalDate cursorRecordAt) { + if (cursorRecordAt == null) { return null; } - return memory.id.lt(cursorId); + return memory.recordAt.lt(cursorRecordAt); } - private BooleanExpression gtCursorId(Long cursorId) { - if (cursorId == null) { + private BooleanExpression gtCursorRecordAt(LocalDate cursorRecordAt) { + if (cursorRecordAt == null) { return null; } - return memory.id.gt(cursorId); + return memory.recordAt.gt(cursorRecordAt); } private BooleanExpression goeRecordAt(LocalDate recordAt) { diff --git a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java index ecb1169c..af8a86d1 100644 --- a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java +++ b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java @@ -11,6 +11,7 @@ import com.depromeet.member.repository.MemberRepositoryImpl; import com.depromeet.memory.Memory; import com.depromeet.memory.MemoryDetail; +import com.depromeet.memory.Timeline; import com.querydsl.jpa.impl.JPAQueryFactory; import java.time.LocalDate; import java.util.List; @@ -20,18 +21,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; import org.springframework.test.context.junit.jupiter.SpringExtension; @DataJpaTest @Import(TestQueryDslConfig.class) @ExtendWith(SpringExtension.class) public class MemoryRepositoryTest { - private Pageable pageable; - @Autowired private JPAQueryFactory queryFactory; @Autowired private MemoryJpaRepository memoryJpaRepository; private MemoryRepositoryImpl memoryRepositoryImpl; @@ -45,8 +40,6 @@ public class MemoryRepositoryTest { @BeforeEach void setUp() { - pageable = getPageable(); - memberRepositoryImpl = new MemberRepositoryImpl(memberJpaRepository); memoryRepositoryImpl = new MemoryRepositoryImpl(queryFactory, memoryJpaRepository); memoryDetailRepositoryImpl = new MemoryDetailRepositoryImpl(memoryDetailJpaRepository); @@ -62,16 +55,12 @@ void setUp() { } } - private Pageable getPageable() { - return PageRequest.of(0, 30, Sort.by(Sort.Order.desc("recordAt"))); - } - @Test void findPrevMemoryByMemberId로_최근_날짜_이전_30일_recordAt_Desc로_가져오는지_테스트() { // when - Slice resultSlice = - memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, pageable, null); - List result = resultSlice.getContent(); + Timeline timelines = + memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, null); + List result = timelines.getTimelineContents(); Memory lastMemory = result.getLast(); // then @@ -85,10 +74,9 @@ private Pageable getPageable() { LocalDate recordAt = LocalDate.of(2024, 8, 31); // when - Slice resultSlice = - memoryRepositoryImpl.findPrevMemoryByMemberId( - member.getId(), null, pageable, recordAt); - List result = resultSlice.getContent(); + Timeline timelines = + memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); + List result = timelines.getTimelineContents(); Memory lastMemory = result.getLast(); // then @@ -101,18 +89,17 @@ private Pageable getPageable() { // given LocalDate recordAt = LocalDate.of(2024, 8, 31); - Slice initResultSlice = - memoryRepositoryImpl.findPrevMemoryByMemberId( - member.getId(), null, pageable, recordAt); + Timeline initTimelines = + memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); - List initResultSliceList = initResultSlice.getContent(); - Memory lastDate = initResultSliceList.getLast(); + List timelineContents = initTimelines.getTimelineContents(); + Memory lastDate = timelineContents.getLast(); // when - Slice resultSlice = + Timeline timelines = memoryRepositoryImpl.findPrevMemoryByMemberId( - member.getId(), lastDate.getId(), pageable, null); - List result = resultSlice.getContent(); + member.getId(), lastDate.getRecordAt(), null); + List result = timelines.getTimelineContents(); // then assertThat(result.size()).isEqualTo(30); @@ -124,18 +111,17 @@ private Pageable getPageable() { // given LocalDate recordAt = LocalDate.of(2024, 8, 31); - Slice initResultSlice = - memoryRepositoryImpl.findPrevMemoryByMemberId( - member.getId(), null, pageable, recordAt); + Timeline initTimeline = + memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); - List initResultSliceList = initResultSlice.getContent(); - Memory firstDate = initResultSliceList.getFirst(); + List initTimelineContents = initTimeline.getTimelineContents(); + Memory firstDate = initTimelineContents.getFirst(); // when - Slice resultSlice = + Timeline resultSlice = memoryRepositoryImpl.findNextMemoryByMemberId( - member.getId(), firstDate.getId(), pageable, null); - List result = resultSlice.getContent(); + member.getId(), firstDate.getRecordAt(), null); + List result = resultSlice.getTimelineContents(); // then assertThat(result.size()).isEqualTo(30); diff --git a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java index add94d5a..52bc16ef 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java +++ b/module-presentation/src/main/java/com/depromeet/memory/api/MemoryController.java @@ -47,7 +47,7 @@ public ApiResponse update( @GetMapping("/timeline") public ApiResponse timeline( - @LoginMember Long memberId, @ModelAttribute TimelineRequestDto timelineRequestDto) { + @LoginMember Long memberId, TimelineRequestDto timelineRequestDto) { CustomSliceResponse result = memoryFacade.getTimelineByMemberIdAndCursorAndDate(memberId, timelineRequestDto); return ApiResponse.success(MemorySuccessType.GET_TIMELINE_SUCCESS, result); diff --git a/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java b/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java index 7a9fb241..fb81c753 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java +++ b/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java @@ -1,8 +1,6 @@ package com.depromeet.memory.dto.request; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotNull; import java.time.YearMonth; import lombok.*; import org.springframework.format.annotation.DateTimeFormat; @@ -20,9 +18,4 @@ public class TimelineRequestDto { @Builder.Default @Schema(description = "조회하고 싶은 날짜 조회 이후, 날짜 기준 이전 정보를 보고 싶다면 false, 이후 정보를 보고 싶다면 true") private boolean showNewer = false; - - @Min(1) - @NotNull - @Schema(description = "페이지 크기") - private Integer size; } From d01fe89c91eec87b234e96f57d02d4248c9fc89b Mon Sep 17 00:00:00 2001 From: penrose15 Date: Wed, 24 Jul 2024 23:28:46 +0900 Subject: [PATCH 09/11] test: fix test in MemoryRepositoryTest --- .../memory/repository/MemoryRepositoryTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java index af8a86d1..fba7a2d1 100644 --- a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java +++ b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java @@ -64,8 +64,8 @@ void setUp() { Memory lastMemory = result.getLast(); // then - assertThat(result.size()).isEqualTo(30); - assertThat(lastMemory.getRecordAt()).isEqualTo(startRecordAt.minusDays(30)); + assertThat(result.size()).isEqualTo(10); + assertThat(lastMemory.getRecordAt()).isEqualTo(startRecordAt.minusDays(10)); } @Test @@ -80,8 +80,8 @@ void setUp() { Memory lastMemory = result.getLast(); // then - assertThat(result.size()).isEqualTo(30); - assertThat(lastMemory.getRecordAt()).isEqualTo(recordAt.minusDays(29)); + assertThat(result.size()).isEqualTo(10); + assertThat(lastMemory.getRecordAt()).isEqualTo(recordAt.minusDays(9)); } @Test @@ -102,8 +102,8 @@ void setUp() { List result = timelines.getTimelineContents(); // then - assertThat(result.size()).isEqualTo(30); - assertThat(result.getLast().getRecordAt()).isEqualTo(lastDate.getRecordAt().minusDays(30)); + assertThat(result.size()).isEqualTo(10); + assertThat(result.getLast().getRecordAt()).isEqualTo(lastDate.getRecordAt().minusDays(10)); } @Test @@ -124,7 +124,7 @@ void setUp() { List result = resultSlice.getTimelineContents(); // then - assertThat(result.size()).isEqualTo(30); - assertThat(result.getFirst().getRecordAt()).isEqualTo(firstDate.getRecordAt().plusDays(30)); + assertThat(result.size()).isEqualTo(10); + assertThat(result.getFirst().getRecordAt()).isEqualTo(firstDate.getRecordAt().plusDays(10)); } } From 884c5309ba03e53c79584620833f843da48b3106 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Wed, 24 Jul 2024 23:33:04 +0900 Subject: [PATCH 10/11] test: delete show-sql --- .../src/test/resources/application.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/module-infrastructure/persistence-database/src/test/resources/application.yml b/module-infrastructure/persistence-database/src/test/resources/application.yml index 94b7319a..45de6a26 100644 --- a/module-infrastructure/persistence-database/src/test/resources/application.yml +++ b/module-infrastructure/persistence-database/src/test/resources/application.yml @@ -11,9 +11,4 @@ spring: jpa: generate-ddl: true hibernate: - ddl-auto: create - properties: - hibernate: - show_sql: true - format_sql: true - use_sql_comments: true \ No newline at end of file + ddl-auto: create \ No newline at end of file From 96387cde2d65e6e42cbd21089c94598ac0befb07 Mon Sep 17 00:00:00 2001 From: penrose15 Date: Thu, 25 Jul 2024 18:17:45 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=ED=83=80=EC=9E=84=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD(cursorId=20->=20cursorRecordAt)=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/depromeet/memory/Timeline.java | 15 +++++--- .../dto/response/CustomSliceResponse.java | 3 +- .../memory/repository/MemoryRepository.java | 6 ++-- .../repository/MemoryRepositoryImpl.java | 16 ++++----- .../repository/MemoryRepositoryTest.java | 12 +++---- .../dto/request/TimelineRequestDto.java | 6 ++-- .../memory/service/TimelineServiceImpl.java | 35 ++++++++++--------- .../memory/mock/FakeMemoryRepository.java | 4 +-- 8 files changed, 53 insertions(+), 44 deletions(-) diff --git a/module-domain/src/main/java/com/depromeet/memory/Timeline.java b/module-domain/src/main/java/com/depromeet/memory/Timeline.java index 91de8cb9..27773c2b 100644 --- a/module-domain/src/main/java/com/depromeet/memory/Timeline.java +++ b/module-domain/src/main/java/com/depromeet/memory/Timeline.java @@ -1,5 +1,6 @@ package com.depromeet.memory; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import lombok.Builder; @@ -8,17 +9,21 @@ @Getter @NoArgsConstructor -public class Timeline { - private List timelineContents; +public class Timeline { + private List timelineContents; private int pageSize; - private Long cursorId; + private LocalDate cursorRecordAt; private boolean hasNext; @Builder - public Timeline(List timelineContents, int pageSize, Long cursorId, boolean hasNext) { + public Timeline( + List timelineContents, + int pageSize, + LocalDate cursorRecordAt, + boolean hasNext) { this.timelineContents = timelineContents != null ? timelineContents : new ArrayList<>(); this.pageSize = pageSize != 0 ? pageSize : 10; - this.cursorId = cursorId; + this.cursorRecordAt = cursorRecordAt; this.hasNext = hasNext; } } diff --git a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java index ff1a42de..1a093059 100644 --- a/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java +++ b/module-independent/src/main/java/com/depromeet/dto/response/CustomSliceResponse.java @@ -2,7 +2,8 @@ import lombok.Builder; -public record CustomSliceResponse(T content, int pageSize, Long cursorId, boolean hasNext) { +public record CustomSliceResponse( + T content, int pageSize, String cursorRecordAt, boolean hasNext) { @Builder public CustomSliceResponse {} } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java index 211b8565..a33d1b4c 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepository.java @@ -15,11 +15,9 @@ public interface MemoryRepository { Optional update(Long memoryId, Memory memoryUpdate); - Timeline findPrevMemoryByMemberId( - Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); + Timeline findPrevMemoryByMemberId(Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); - Timeline findNextMemoryByMemberId( - Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); + Timeline findNextMemoryByMemberId(Long memberId, LocalDate cursorRecordAt, LocalDate recordAt); List getCalendarByYearAndMonth(Long memberId, Integer year, Short month); } diff --git a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java index c1900ffc..bb1a5bd3 100644 --- a/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java +++ b/module-infrastructure/persistence-database/src/main/java/com/depromeet/memory/repository/MemoryRepositoryImpl.java @@ -80,7 +80,7 @@ public Optional update(Long memoryId, Memory memoryUpdate) { } @Override - public Timeline findPrevMemoryByMemberId( + public Timeline findPrevMemoryByMemberId( Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "recordAt"); @@ -97,25 +97,25 @@ public Timeline findPrevMemoryByMemberId( List content = toModel(result); boolean hasNext = false; - Long nextMemoryId = null; + LocalDate nextMemoryRecordAt = null; if (content.size() > pageable.getPageSize()) { content = new ArrayList<>(content); content.removeLast(); hasNext = true; Memory lastMemory = content.getLast(); - nextMemoryId = lastMemory.getId(); + nextMemoryRecordAt = lastMemory.getRecordAt(); } return Timeline.builder() .timelineContents(content) .pageSize(10) - .cursorId(nextMemoryId) + .cursorRecordAt(nextMemoryRecordAt) .hasNext(hasNext) .build(); } @Override - public Timeline findNextMemoryByMemberId( + public Timeline findNextMemoryByMemberId( Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "recordAt"); @@ -132,20 +132,20 @@ public Timeline findNextMemoryByMemberId( List content = toModel(result); boolean hasNext = false; - Long nextMemoryId = null; + LocalDate nextMemoryRecordAt = null; if (content.size() > pageable.getPageSize()) { content = new ArrayList<>(content); content.removeLast(); hasNext = true; Memory lastMemory = content.getLast(); - nextMemoryId = lastMemory.getId(); + nextMemoryRecordAt = lastMemory.getRecordAt(); } content = content.reversed(); return Timeline.builder() .timelineContents(content) .pageSize(10) - .cursorId(nextMemoryId) + .cursorRecordAt(nextMemoryRecordAt) .hasNext(hasNext) .build(); } diff --git a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java index fba7a2d1..b0b939f3 100644 --- a/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java +++ b/module-infrastructure/persistence-database/src/test/java/com/depromeet/memory/repository/MemoryRepositoryTest.java @@ -58,7 +58,7 @@ void setUp() { @Test void findPrevMemoryByMemberId로_최근_날짜_이전_30일_recordAt_Desc로_가져오는지_테스트() { // when - Timeline timelines = + Timeline timelines = memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, null); List result = timelines.getTimelineContents(); Memory lastMemory = result.getLast(); @@ -74,7 +74,7 @@ void setUp() { LocalDate recordAt = LocalDate.of(2024, 8, 31); // when - Timeline timelines = + Timeline timelines = memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); List result = timelines.getTimelineContents(); Memory lastMemory = result.getLast(); @@ -89,14 +89,14 @@ void setUp() { // given LocalDate recordAt = LocalDate.of(2024, 8, 31); - Timeline initTimelines = + Timeline initTimelines = memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); List timelineContents = initTimelines.getTimelineContents(); Memory lastDate = timelineContents.getLast(); // when - Timeline timelines = + Timeline timelines = memoryRepositoryImpl.findPrevMemoryByMemberId( member.getId(), lastDate.getRecordAt(), null); List result = timelines.getTimelineContents(); @@ -111,14 +111,14 @@ void setUp() { // given LocalDate recordAt = LocalDate.of(2024, 8, 31); - Timeline initTimeline = + Timeline initTimeline = memoryRepositoryImpl.findPrevMemoryByMemberId(member.getId(), null, recordAt); List initTimelineContents = initTimeline.getTimelineContents(); Memory firstDate = initTimelineContents.getFirst(); // when - Timeline resultSlice = + Timeline resultSlice = memoryRepositoryImpl.findNextMemoryByMemberId( member.getId(), firstDate.getRecordAt(), null); List result = resultSlice.getTimelineContents(); diff --git a/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java b/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java index fb81c753..1b719461 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java +++ b/module-presentation/src/main/java/com/depromeet/memory/dto/request/TimelineRequestDto.java @@ -1,6 +1,7 @@ package com.depromeet.memory.dto.request; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; import java.time.YearMonth; import lombok.*; import org.springframework.format.annotation.DateTimeFormat; @@ -8,8 +9,9 @@ @Getter @Builder public class TimelineRequestDto { - @Schema(description = "최초 조회 이후 나온 timeline 리스트 중 가장 마지막 요소의 memory PK") - private Long cursorId; + @DateTimeFormat(pattern = "yyyy-MM-dd") + @Schema(description = "최초 조회 이후 나온 timeline 리스트 중 가장 마지막 요소의 memory recordAt") + private LocalDate cursorRecordAt; @DateTimeFormat(pattern = "yyyy-MM") @Schema(description = "조회하고 싶은 날짜, yyyy-MM") diff --git a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java index 37afd9b5..a0f85e87 100644 --- a/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java +++ b/module-presentation/src/main/java/com/depromeet/memory/service/TimelineServiceImpl.java @@ -1,7 +1,6 @@ package com.depromeet.memory.service; import com.depromeet.dto.response.CustomSliceResponse; -import com.depromeet.exception.NotFoundException; import com.depromeet.image.Image; import com.depromeet.image.dto.response.MemoryImagesDto; import com.depromeet.memory.Memory; @@ -11,7 +10,6 @@ import com.depromeet.memory.dto.response.StrokeResponse; import com.depromeet.memory.dto.response.TimelineResponseDto; import com.depromeet.memory.repository.MemoryRepository; -import com.depromeet.type.memory.MemoryErrorType; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; @@ -33,19 +31,15 @@ public class TimelineServiceImpl implements TimelineService { @Override public CustomSliceResponse getTimelineByMemberIdAndCursorAndDate( Long memberId, TimelineRequestDto timeline) { - Timeline timelines = getTimelines(memberId, timeline); + Timeline timelines = getTimelines(memberId, timeline); return mapToCustomSliceResponse(timelines); } - private Timeline getTimelines(Long memberId, TimelineRequestDto timeline) { + private Timeline getTimelines(Long memberId, TimelineRequestDto timeline) { LocalDate cursorRecordAt = null; - if (timeline.getCursorId() != null) { - Memory memory = - memoryRepository - .findById(timeline.getCursorId()) - .orElseThrow(() -> new NotFoundException(MemoryErrorType.NOT_FOUND)); - cursorRecordAt = memory.getRecordAt(); + if (timeline.getCursorRecordAt() != null) { + cursorRecordAt = timeline.getCursorRecordAt(); } LocalDate parsedDate = getLocalDateOrNull(timeline.getDate()); @@ -57,7 +51,7 @@ private Timeline getTimelines(Long memberId, TimelineRequestDto timeline } } - private CustomSliceResponse mapToCustomSliceResponse(Timeline timelines) { + private CustomSliceResponse mapToCustomSliceResponse(Timeline timelines) { List result = timelines.getTimelineContents().stream() .map(this::mapToTimelineResponseDto) @@ -66,7 +60,7 @@ private CustomSliceResponse mapToCustomSliceResponse(Timeline timelin return CustomSliceResponse.builder() .content(result) .pageSize(timelines.getPageSize()) - .cursorId(timelines.getCursorId()) + .cursorRecordAt(getCursorRecordAtResponse(timelines)) .hasNext(timelines.isHasNext()) .build(); } @@ -79,6 +73,12 @@ private LocalDate getLocalDateOrNull(YearMonth date) { return lastDayOfMonth; } + private String getCursorRecordAtResponse(Timeline timelines) { + return timelines.getCursorRecordAt() != null + ? timelines.getCursorRecordAt().toString() + : null; + } + @Override public TimelineResponseDto mapToTimelineResponseDto(Memory memory) { return TimelineResponseDto.builder() @@ -89,10 +89,7 @@ public TimelineResponseDto mapToTimelineResponseDto(Memory memory) { .lane(memory.getLane()) .diary(memory.getDiary()) .totalMeter(calculateTotalMeter(memory.getStrokes(), memory.getLane())) - .memoryDetailId( - memory.getMemoryDetail() != null && memory.getMemoryDetail().getId() != null - ? memory.getMemoryDetail().getId() - : null) + .memoryDetailId(getMemoryDetailId(memory)) .item(getItemFromMemoryDetail(memory)) .heartRate(getHeartRateFromMemoryDetail(memory)) .pace(getPaceFromMemoryDetail(memory)) @@ -149,6 +146,12 @@ private Integer calculateTotalMeter(List strokes, Short lane) { return totalMeter; } + private Long getMemoryDetailId(Memory memory) { + return memory.getMemoryDetail() != null && memory.getMemoryDetail().getId() != null + ? memory.getMemoryDetail().getId() + : null; + } + private List strokeToDto(List strokes) { if (strokes == null || strokes.isEmpty()) return null; diff --git a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java index 1ff787fc..44e3ed96 100644 --- a/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java +++ b/module-presentation/src/test/java/com/depromeet/memory/mock/FakeMemoryRepository.java @@ -98,13 +98,13 @@ public Optional update(Long memoryId, Memory memoryUpdate) { } @Override - public Timeline findPrevMemoryByMemberId( + public Timeline findPrevMemoryByMemberId( Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { return null; } @Override - public Timeline findNextMemoryByMemberId( + public Timeline findNextMemoryByMemberId( Long memberId, LocalDate cursorRecordAt, LocalDate recordAt) { return null; }