From 39da13d5f93b1bf835b74e951bae8121a3758fcf Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Mon, 26 May 2025 15:34:26 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 회원 월별 기록 조회 api #41 --- .DS_Store | Bin 6148 -> 6148 bytes .../history/api/HistoryRestController.java | 46 ++++++++++++++++ .../application/HistoryImageQueryService.java | 4 ++ .../HistoryImageQueryServiceImpl.java | 4 ++ .../history/application/HistoryService.java | 10 ++++ .../application/HistoryServiceImpl.java | 51 ++++++++++++++++++ .../history/converter/HistoryConverter.java | 40 ++++++++++++++ .../repository/HistoryImageRepository.java | 3 ++ .../domain/repository/HistoryRepository.java | 5 ++ .../domain/history/dto/HistoryRequestDTO.java | 4 ++ .../history/dto/HistoryResponseDTO.java | 30 +++++++++++ .../history/exception/HistoryExeption.java | 10 ++++ .../goorm/global/config/SwaggerConfig.java | 13 +---- .../error/code/status/SuccessStatus.java | 5 +- 14 files changed, 212 insertions(+), 13 deletions(-) create mode 100644 goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java create mode 100644 goorm/src/main/java/study/goorm/domain/history/exception/HistoryExeption.java diff --git a/.DS_Store b/.DS_Store index edd3b724f16ca617442e9da861cc2ddf33a16170..a299d221be6b397f989dcef09b4109e4b86045c4 100644 GIT binary patch delta 72 zcmZoMXfc@JFUrEez`)4BAi%(o!BEPOlv13Wla#+%ka;lgH?wp6 getMonthlyHistories( + @RequestParam(value = "clokeyId") String clokeyId, + @RequestParam LocalDate month + ) { + HistoryResponseDTO.HistoryGetMonthly result = historyService.getHistoryGetMonthly(clokeyId, month); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); + } +} diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java new file mode 100644 index 0000000..77103b3 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java @@ -0,0 +1,4 @@ +package study.goorm.domain.history.application; + +public interface HistoryImageQueryService { +} diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java new file mode 100644 index 0000000..94842fe --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java @@ -0,0 +1,4 @@ +package study.goorm.domain.history.application; + +public class HistoryImageQueryServiceImpl { +} diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java new file mode 100644 index 0000000..7edafc8 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -0,0 +1,10 @@ +package study.goorm.domain.history.application; + +import study.goorm.domain.history.dto.HistoryRequestDTO; +import study.goorm.domain.history.dto.HistoryResponseDTO; + +import java.time.LocalDate; + +public interface HistoryService { + HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); +} diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java new file mode 100644 index 0000000..3efc82d --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -0,0 +1,51 @@ +package study.goorm.domain.history.application; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import study.goorm.domain.cloth.domain.entity.Cloth; +import study.goorm.domain.history.converter.HistoryConverter; +import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.domain.repository.HistoryImageRepository; +import study.goorm.domain.history.domain.repository.HistoryRepository; +import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.member.domain.entity.Member; +import study.goorm.domain.member.domain.exception.MemberException; +import study.goorm.domain.member.domain.repository.MemberRepository; +import study.goorm.domain.model.enums.ClothSort; +import study.goorm.global.error.code.status.ErrorStatus; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class HistoryServiceImpl implements HistoryService { + + private final HistoryRepository historyRepository; + private final HistoryImageRepository historyImageRepository; + private final MemberRepository memberRepository; + + @Override + @Transactional(readOnly = true) + public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month) { + Member member = memberRepository.findByClokeyId(clokeyId) + .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + + // 해당 월의 시작일과 마지막일 계산 + LocalDate startOfMonth = month.withDayOfMonth(1); + LocalDate endOfMonth = month.withDayOfMonth(month.lengthOfMonth()); + + List histories = historyRepository.findAllByMemberAndHistoryDateBetween(member, startOfMonth, endOfMonth); + List historyIds = histories.stream() + .map(History::getId) + .toList(); + List historyImages = historyImageRepository.findAllByHistoryIdIn(historyIds); + + return HistoryConverter.toHistoryGetMonthly(member, month, histories, historyImages); + } +} diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java new file mode 100644 index 0000000..28b67cb --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -0,0 +1,40 @@ +package study.goorm.domain.history.converter; + +import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.member.domain.entity.Member; + +import java.time.LocalDate; +import java.util.List; + +public class HistoryConverter { + + public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member member, LocalDate month, List histories, List historyImages){ + return HistoryResponseDTO.HistoryGetMonthly.builder() + .memberId(member.getId()) + .nickName(member.getNickname()) + .histories(toHistoryGetList(histories, historyImages)) + .build(); + } + + private static List toHistoryGetList(List histories, List historyImages) { + return histories.stream() + .map(history -> { + HistoryImage image = historyImages.stream() + .filter(img -> img.getHistory().getId().equals(history.getId())) + .findFirst() + .orElse(null); + return toHistoryGet(history, image); + }) + .collect(java.util.stream.Collectors.toList()); + } + + private static HistoryResponseDTO.HistoryGet toHistoryGet(History history, HistoryImage historyImage){ + return HistoryResponseDTO.HistoryGet.builder() + .historyId(history.getId()) + .date(history.getHistoryDate()) + .imageUrl(historyImage != null ? historyImage.getImageUrl() : "비공개입니다") + .build(); + } +} diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java index d07af9b..3a1e8a5 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java @@ -3,5 +3,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.HistoryImage; +import java.util.List; + public interface HistoryImageRepository extends JpaRepository{ + List findAllByHistoryIdIn(List historyIds); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java index 47df229..5781c5b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java @@ -2,6 +2,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.member.domain.entity.Member; + +import java.time.LocalDate; +import java.util.List; public interface HistoryRepository extends JpaRepository{ + List findAllByMemberAndHistoryDateBetween(Member member, LocalDate start, LocalDate end); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java new file mode 100644 index 0000000..87594c7 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java @@ -0,0 +1,4 @@ +package study.goorm.domain.history.dto; + +public class HistoryRequestDTO { +} diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java new file mode 100644 index 0000000..8fee729 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -0,0 +1,30 @@ +package study.goorm.domain.history.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.util.List; + +public class HistoryResponseDTO { + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryGetMonthly { + private Long memberId; + private String nickName; + private List histories; + } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryGet { + private Long historyId; + private LocalDate date; + private String imageUrl; + } +} diff --git a/goorm/src/main/java/study/goorm/domain/history/exception/HistoryExeption.java b/goorm/src/main/java/study/goorm/domain/history/exception/HistoryExeption.java new file mode 100644 index 0000000..0d1b165 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/exception/HistoryExeption.java @@ -0,0 +1,10 @@ +package study.goorm.domain.history.exception; + +import study.goorm.global.error.code.BaseErrorCode; +import study.goorm.global.exception.GeneralException; + +public class HistoryExeption extends GeneralException { + public HistoryExeption(BaseErrorCode code) { + super(code); + } +} diff --git a/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java b/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java index faac566..2dfd637 100644 --- a/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java +++ b/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java @@ -19,17 +19,6 @@ @Configuration public class SwaggerConfig { - /** - * Multipart + JSON 동시에 Swagger에서 테스트할 때 - * content-type null → application/octet-stream 으로 처리되어 Jackson에서 인식 못하는 문제를 해결 - */ - @Autowired - public void configureMessageConverter(MappingJackson2HttpMessageConverter converter) { - List supportMediaTypes = new ArrayList<>(converter.getSupportedMediaTypes()); - supportMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); - converter.setSupportedMediaTypes(supportMediaTypes); - } - @Bean public OpenAPI goormStudyAPI() { Info info = new Info() @@ -52,4 +41,4 @@ public OpenAPI goormStudyAPI() { .addSecurityItem(securityRequirement) .components(components); } -} +} \ No newline at end of file diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index d29d3b2..0cc3e58 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -16,7 +16,10 @@ public enum SuccessStatus implements BaseCode { //Cloth CLOTH_VIEW_SUCCESS(HttpStatus.OK,"CLOTH_200","옷이 성공적으로 조회되었습니다."), CLOTH_CREATED(HttpStatus.CREATED, "CLOTH_201"," 옷이 성공적으로 생성되었습니다."), - CLOTH_DELETED(HttpStatus.NO_CONTENT,"CLOTH_202","옷이 성공적으로 삭제되었습니다");; + CLOTH_DELETED(HttpStatus.NO_CONTENT,"CLOTH_202","옷이 성공적으로 삭제되었습니다"), + + //History + HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."); private final HttpStatus httpStatus; private final String code; From 8534b3bd4ef24af8564d7c48bb3d3081f37eef32 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Tue, 27 May 2025 22:29:30 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 기록 삭제 api 구현 #41 --- .../history/api/HistoryRestController.java | 17 +++++++++++++++ .../history/application/HistoryService.java | 1 + .../application/HistoryServiceImpl.java | 21 +++++++++++++++++++ .../repository/HashtagHistoryRepository.java | 2 ++ .../repository/HistoryClothRepository.java | 2 ++ .../repository/HistoryImageRepository.java | 2 ++ .../global/error/code/status/ErrorStatus.java | 5 ++++- .../error/code/status/SuccessStatus.java | 4 +++- 8 files changed, 52 insertions(+), 2 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 241aa9c..0c57f90 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -43,4 +43,21 @@ public BaseResponse getMonthlyHistories( return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); } + @DeleteMapping("/{historyId}") + @Operation(summary = "특정 기록을 삭제하는 API", description = "path variable로 historyId를 넘겨주세요.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "OK, 성공적으로 삭제되었습니다."), + }) + @Parameters({ + @Parameter(name = "historyId", description = "기록의 id, path variable 입니다.") + }) + public BaseResponse deleteHistory( + @PathVariable(value = "historyId") Long historyId + ) { + + historyService.deleteHistory(historyId); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_DELETED, null); + } + } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index 7edafc8..cda7a39 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -7,4 +7,5 @@ public interface HistoryService { HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); + void deleteHistory(Long historyId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 3efc82d..714fc53 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -9,9 +9,12 @@ import study.goorm.domain.history.converter.HistoryConverter; import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.domain.repository.HashtagHistoryRepository; +import study.goorm.domain.history.domain.repository.HistoryClothRepository; import study.goorm.domain.history.domain.repository.HistoryImageRepository; import study.goorm.domain.history.domain.repository.HistoryRepository; import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.history.exception.HistoryExeption; import study.goorm.domain.member.domain.entity.Member; import study.goorm.domain.member.domain.exception.MemberException; import study.goorm.domain.member.domain.repository.MemberRepository; @@ -29,6 +32,9 @@ public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; private final HistoryImageRepository historyImageRepository; private final MemberRepository memberRepository; + private final HashtagHistoryRepository hashtagHistoryRepository; + private final HistoryClothRepository historyClothRepository; + @Override @Transactional(readOnly = true) @@ -48,4 +54,19 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId return HistoryConverter.toHistoryGetMonthly(member, month, histories, historyImages); } + + @Override + @Transactional + public void deleteHistory(Long historyId) { + History history = historyRepository.findById(historyId) + .orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + + //매핑 테이블 삭제 + hashtagHistoryRepository.deleteByHistory(history); + historyImageRepository.deleteByHistory(history); + historyClothRepository.deleteByHistory(history); + + //최종 옷 삭제 + historyRepository.delete(history); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java index a2ae404..476ed5c 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java @@ -2,6 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.HashtagHistory; +import study.goorm.domain.history.domain.entity.History; public interface HashtagHistoryRepository extends JpaRepository{ + void deleteByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java index ce17a30..3c26af5 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java @@ -2,8 +2,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.cloth.domain.entity.Cloth; +import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryCloth; public interface HistoryClothRepository extends JpaRepository{ void deleteAllByCloth(Cloth cloth); + void deleteByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java index 3a1e8a5..dae5c91 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java @@ -1,10 +1,12 @@ package study.goorm.domain.history.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryImage; import java.util.List; public interface HistoryImageRepository extends JpaRepository{ List findAllByHistoryIdIn(List historyIds); + void deleteByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index e8cd8b0..20985e1 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -28,8 +28,11 @@ public enum ErrorStatus implements BaseErrorCode { // ErrorStatus에 에러 추가 ! NO_SUCH_CATEGORY(HttpStatus.BAD_REQUEST, "CLOTH_4003", "카테고리가 존재하지 않습니다."), - LOWER_TEMP_BIGGER_THAN_UPPER_TEMP(HttpStatus.BAD_REQUEST,"CLOTH_4004","옷의 하한 온도가 상한 온도 보다 높습니다."); + LOWER_TEMP_BIGGER_THAN_UPPER_TEMP(HttpStatus.BAD_REQUEST,"CLOTH_4004","옷의 하한 온도가 상한 온도 보다 높습니다."), + // History + NO_SUCH_HISTORY(HttpStatus.BAD_REQUEST, "HISTORY_4002","존재하지 않는 기록 ID 입니다.") + ; private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index 0cc3e58..36daf40 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import study.goorm.domain.history.domain.entity.History; import study.goorm.global.error.code.BaseCode; import study.goorm.global.error.code.ReasonDTO; @@ -19,7 +20,8 @@ public enum SuccessStatus implements BaseCode { CLOTH_DELETED(HttpStatus.NO_CONTENT,"CLOTH_202","옷이 성공적으로 삭제되었습니다"), //History - HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."); + HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."), + HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_201","기록이 성공적으로 삭제되었습니다."); private final HttpStatus httpStatus; private final String code; From b931d7c364b7d351bf9b3405aa7a4a001c9cbaad Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 01:17:20 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로운 기록 생성 api #41 --- .../domain/repository/ClothRepository.java | 3 ++ .../history/api/HistoryRestController.java | 18 +++++++++++- .../history/application/HistoryService.java | 2 ++ .../application/HistoryServiceImpl.java | 29 +++++++++++++++++++ .../history/converter/HistoryConverter.java | 6 ++++ .../domain/history/dto/HistoryRequestDTO.java | 27 +++++++++++++++++ .../history/dto/HistoryResponseDTO.java | 8 +++++ .../error/code/status/SuccessStatus.java | 3 +- 8 files changed, 94 insertions(+), 2 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java b/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java index a0ead3a..5546f40 100644 --- a/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java @@ -6,6 +6,8 @@ import study.goorm.domain.cloth.domain.entity.Cloth; import study.goorm.domain.member.domain.entity.Member; +import java.util.List; + public interface ClothRepository extends JpaRepository{ // 1. wearNum 오름차순 Page findByMemberOrderByWearNumAsc(Member member, Pageable pageable); @@ -18,4 +20,5 @@ public interface ClothRepository extends JpaRepository{ // 4. createdAt 내림차순 Page findByMemberOrderByCreatedAtDesc(Member member, Pageable pageable); + } diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 0c57f90..2d78103 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -5,10 +5,13 @@ import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import study.goorm.domain.cloth.dto.ClothResponseDTO; import study.goorm.domain.history.application.HistoryService; +import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.model.enums.ClothSort; import study.goorm.domain.model.exception.annotation.CheckPage; @@ -46,7 +49,7 @@ public BaseResponse getMonthlyHistories( @DeleteMapping("/{historyId}") @Operation(summary = "특정 기록을 삭제하는 API", description = "path variable로 historyId를 넘겨주세요.") @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "OK, 성공적으로 삭제되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_202", description = "OK, 성공적으로 삭제되었습니다."), }) @Parameters({ @Parameter(name = "historyId", description = "기록의 id, path variable 입니다.") @@ -60,4 +63,17 @@ public BaseResponse deleteHistory( return BaseResponse.onSuccess(SuccessStatus.HISTORY_DELETED, null); } + @PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "새로운 옷 기록을 생성하는 API", description = "request body에 HistoryCreateRequest 형식의 데이터를 전달해주세요.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_201", description = "CREATED, 성공적으로 생성되었습니다."), + }) + public BaseResponse createHistory( + @RequestPart("historyCreateRequest") HistoryRequestDTO.HistoryCreateRequest historyCreateRequest, + @RequestPart("imageFile") MultipartFile imageFile + ) { + HistoryResponseDTO.HistoryCreateResult result = historyService.createHistory(historyCreateRequest,imageFile); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_CREATED, result); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index cda7a39..b5f066b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -1,5 +1,6 @@ package study.goorm.domain.history.application; +import org.springframework.web.multipart.MultipartFile; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; @@ -8,4 +9,5 @@ public interface HistoryService { HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); void deleteHistory(Long historyId); + HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 714fc53..b5bc292 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -5,14 +5,19 @@ import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import study.goorm.domain.cloth.domain.entity.Cloth; +import study.goorm.domain.cloth.domain.repository.ClothRepository; +import study.goorm.domain.cloth.exception.ClothException; import study.goorm.domain.history.converter.HistoryConverter; import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.domain.entity.HistoryCloth; import study.goorm.domain.history.domain.entity.HistoryImage; import study.goorm.domain.history.domain.repository.HashtagHistoryRepository; import study.goorm.domain.history.domain.repository.HistoryClothRepository; import study.goorm.domain.history.domain.repository.HistoryImageRepository; import study.goorm.domain.history.domain.repository.HistoryRepository; +import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.history.exception.HistoryExeption; import study.goorm.domain.member.domain.entity.Member; @@ -34,6 +39,7 @@ public class HistoryServiceImpl implements HistoryService { private final MemberRepository memberRepository; private final HashtagHistoryRepository hashtagHistoryRepository; private final HistoryClothRepository historyClothRepository; + private final ClothRepository clothRepository; @Override @@ -69,4 +75,27 @@ public void deleteHistory(Long historyId) { //최종 옷 삭제 historyRepository.delete(history); } + + @Override + public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image) { + List cloth = clothRepository.findAllById(historyCreateResult.getClothes()); + if (cloth.isEmpty()) { + throw new ClothException(ErrorStatus.NO_SUCH_CLOTH); + } + + + History newHistory = History.builder() + .historyDate(LocalDate.parse(historyCreateResult.getDate())) + .content(historyCreateResult.getContent()) + .build(); + HistoryImage newHistoryImage = HistoryImage.builder() + .history(newHistory) + .imageUrl("url") + .build(); + + historyImageRepository.save(newHistoryImage); + + + return HistoryConverter.toHistoryCreateResult(newHistory); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index 28b67cb..e034652 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -37,4 +37,10 @@ private static HistoryResponseDTO.HistoryGet toHistoryGet(History history, Histo .imageUrl(historyImage != null ? historyImage.getImageUrl() : "비공개입니다") .build(); } + + public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history){ + return HistoryResponseDTO.HistoryCreateResult.builder() + .historyId(history.getId()) + .build(); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java index 87594c7..0869a9d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java @@ -1,4 +1,31 @@ package study.goorm.domain.history.dto; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import study.goorm.domain.cloth.exception.annotation.CheckLowerUpperTempBound; +import study.goorm.domain.model.enums.Season; +import study.goorm.domain.model.enums.ThicknessLevel; + +import java.util.List; + public class HistoryRequestDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryCreateRequest { + + private String content; + + private List clothes; + + private List hashtags; + + private String date; + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 8fee729..9a55952 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -27,4 +27,12 @@ public static class HistoryGet { private LocalDate date; private String imageUrl; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryCreateResult { + private Long historyId; + } } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index 36daf40..5479676 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -21,7 +21,8 @@ public enum SuccessStatus implements BaseCode { //History HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."), - HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_201","기록이 성공적으로 삭제되었습니다."); + HISTORY_CREATED(HttpStatus.CREATED,"HISTORY_201","옷 기록이 성공적으로 생성되었습니다."), + HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."); private final HttpStatus httpStatus; private final String code; From cb09027e7cfef5b77a23ec65601bccff46f57d4d Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 02:06:07 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 옷 기록 수정 - serviceImpl 미작성 #41 --- .../history/api/HistoryRestController.java | 20 +++++++++++++++++++ .../history/application/HistoryService.java | 2 ++ .../application/HistoryServiceImpl.java | 6 ++++++ .../error/code/status/SuccessStatus.java | 3 ++- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 2d78103..44250dd 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; @@ -13,6 +14,7 @@ import study.goorm.domain.history.application.HistoryService; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.member.domain.entity.Member; import study.goorm.domain.model.enums.ClothSort; import study.goorm.domain.model.exception.annotation.CheckPage; import study.goorm.domain.model.exception.annotation.CheckPageSize; @@ -76,4 +78,22 @@ public BaseResponse createHistory( return BaseResponse.onSuccess(SuccessStatus.HISTORY_CREATED, result); } + + @PatchMapping(value = "/{historyId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @Operation(summary = "특정날짜 옷 기록을 수정하는 API", description = "path variable로 historyId를 넘겨주세요.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_203", description = "OK, 기록이 성공적으로 수정되었습니다."), + }) + @Parameters({ + @Parameter(name = "historyId", description = "기록의 id, path variable 입니다."), + }) + public BaseResponse patchHistory( + @PathVariable Long historyId, + @RequestPart("historyUpdateRequest") @Valid HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, + @RequestPart(value = "imageFile", required = false) MultipartFile imageFile + ) { + historyService.updateHistory(historyId, historyUpdateRequest, imageFile); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_UPDATED, null); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index b5f066b..4aa1632 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -3,6 +3,7 @@ import org.springframework.web.multipart.MultipartFile; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.member.domain.entity.Member; import java.time.LocalDate; @@ -10,4 +11,5 @@ public interface HistoryService { HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); void deleteHistory(Long historyId); HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image); + HistoryResponseDTO.HistoryCreateResult updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index b5bc292..03e8820 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -77,6 +77,7 @@ public void deleteHistory(Long historyId) { } @Override + @Transactional public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image) { List cloth = clothRepository.findAllById(historyCreateResult.getClothes()); if (cloth.isEmpty()) { @@ -98,4 +99,9 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi return HistoryConverter.toHistoryCreateResult(newHistory); } + + @Override + public HistoryResponseDTO.HistoryCreateResult updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image) { + return null; + } } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index 5479676..6b75704 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -22,7 +22,8 @@ public enum SuccessStatus implements BaseCode { //History HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."), HISTORY_CREATED(HttpStatus.CREATED,"HISTORY_201","옷 기록이 성공적으로 생성되었습니다."), - HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."); + HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."), + HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_203","성공적으로 수정되었습니다"); private final HttpStatus httpStatus; private final String code; From 44ae725a2b437181b71038db21e869290e1cb809 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 02:28:51 +0900 Subject: [PATCH 05/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 일별 기록 조회 구현 중간 #41 --- .../history/api/HistoryRestController.java | 19 +++++++++++- .../history/application/HistoryService.java | 1 + .../application/HistoryServiceImpl.java | 6 ++++ .../history/converter/HistoryConverter.java | 6 ++++ .../history/dto/HistoryResponseDTO.java | 29 +++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 44250dd..14c4df7 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -48,6 +48,23 @@ public BaseResponse getMonthlyHistories( return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); } + + @GetMapping("/{historyId}") + @Operation(summary = "특정 회원의 특정 일의 기록을 확인할 수 있는 API", description = "path variable로 historyId를 넘겨주세요.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "OK, 일별 기록이 성공적으로 조회되었습니다."), + }) + @Parameters({ + @Parameter(name = "historyId", description = "기록의 id, path variable 입니다.") + }) + public BaseResponse getDailyHistory( + @PathVariable Long historyId + ) { + HistoryResponseDTO.HistoryGetDaily result = historyService.getHistoryGetDaily(historyId)); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); + } + @DeleteMapping("/{historyId}") @Operation(summary = "특정 기록을 삭제하는 API", description = "path variable로 historyId를 넘겨주세요.") @ApiResponses({ @@ -89,7 +106,7 @@ public BaseResponse createHistory( }) public BaseResponse patchHistory( @PathVariable Long historyId, - @RequestPart("historyUpdateRequest") @Valid HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, + @RequestPart("historyUpdateRequest") HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, @RequestPart(value = "imageFile", required = false) MultipartFile imageFile ) { historyService.updateHistory(historyId, historyUpdateRequest, imageFile); diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index 4aa1632..f343e8b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -9,6 +9,7 @@ public interface HistoryService { HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); + HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId); void deleteHistory(Long historyId); HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image); HistoryResponseDTO.HistoryCreateResult updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image); diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 03e8820..86f672d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -61,6 +61,12 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId return HistoryConverter.toHistoryGetMonthly(member, month, histories, historyImages); } + @Override + @Transactional(readOnly = true) + public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { + return null; + } + @Override @Transactional public void deleteHistory(Long historyId) { diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index e034652..6be65f2 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -18,6 +18,12 @@ public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member me .build(); } + public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, HistoryImage historyImage){ + return HistoryResponseDTO.HistoryGetDaily.builder() + + .build(); + } + private static List toHistoryGetList(List histories, List historyImages) { return histories.stream() .map(history -> { diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 9a55952..41a659b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -28,6 +28,35 @@ public static class HistoryGet { private String imageUrl; } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryGetDaily { + private Long memberId; + private String memberImageUrl; + private String nickName; + private String clokeyId; + private String content; + private List images; + private List hashtags; + private int likeCount; + private boolean liked; + private LocalDate date; + private List clothes; + private long clothId; + private long historyId; + } + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryGetCloth { + private Long clothId; + private String clothImageUrl; + private String clothName; + } + @Builder @Getter @NoArgsConstructor From 93c88787d9deed6fbe9ead3f6b0fe26d455de3ed Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 16:39:02 +0900 Subject: [PATCH 06/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기록 추가 및 수정 api 수정 #41 --- .../history/api/HistoryRestController.java | 2 +- .../application/HistoryImageQueryService.java | 6 +++ .../HistoryImageQueryServiceImpl.java | 31 ++++++++++- .../history/application/HistoryService.java | 2 +- .../application/HistoryServiceImpl.java | 51 ++++++++++++++----- .../domain/history/domain/entity/History.java | 2 + .../repository/HistoryClothRepository.java | 1 + 7 files changed, 79 insertions(+), 16 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 14c4df7..d5e263b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -60,7 +60,7 @@ public BaseResponse getMonthlyHistories( public BaseResponse getDailyHistory( @PathVariable Long historyId ) { - HistoryResponseDTO.HistoryGetDaily result = historyService.getHistoryGetDaily(historyId)); + HistoryResponseDTO.HistoryGetDaily result = historyService.getHistoryGetDaily(historyId); return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java index 77103b3..e54a78e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryService.java @@ -1,4 +1,10 @@ package study.goorm.domain.history.application; +import study.goorm.domain.cloth.domain.entity.Cloth; +import study.goorm.domain.history.domain.entity.History; + +import java.util.Map; + public interface HistoryImageQueryService { + Map getFirstHistoryImageUrlMap(Iterable histories); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java index 94842fe..4f13bf4 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryImageQueryServiceImpl.java @@ -1,4 +1,33 @@ package study.goorm.domain.history.application; -public class HistoryImageQueryServiceImpl { +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.domain.repository.HistoryImageRepository; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@Service +@RequiredArgsConstructor +public class HistoryImageQueryServiceImpl implements HistoryImageQueryService { + + private final HistoryImageRepository historyImageRepository; + + @Override + public Map getFirstHistoryImageUrlMap(Iterable histories) { + List historyIds = StreamSupport.stream(histories.spliterator(), false) + .map(History::getId) + .toList(); + + List firstHistoryImages = historyImageRepository.findAllById(historyIds); + return firstHistoryImages.stream() + .collect(Collectors.toMap( + image -> image.getHistory().getId(), + HistoryImage::getImageUrl + )); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index f343e8b..94fddc2 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -12,5 +12,5 @@ public interface HistoryService { HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId); void deleteHistory(Long historyId); HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image); - HistoryResponseDTO.HistoryCreateResult updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image); + void updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 86f672d..1f6207c 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -1,8 +1,6 @@ package study.goorm.domain.history.application; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -11,7 +9,6 @@ import study.goorm.domain.cloth.exception.ClothException; import study.goorm.domain.history.converter.HistoryConverter; import study.goorm.domain.history.domain.entity.History; -import study.goorm.domain.history.domain.entity.HistoryCloth; import study.goorm.domain.history.domain.entity.HistoryImage; import study.goorm.domain.history.domain.repository.HashtagHistoryRepository; import study.goorm.domain.history.domain.repository.HistoryClothRepository; @@ -23,12 +20,10 @@ import study.goorm.domain.member.domain.entity.Member; import study.goorm.domain.member.domain.exception.MemberException; import study.goorm.domain.member.domain.repository.MemberRepository; -import study.goorm.domain.model.enums.ClothSort; import study.goorm.global.error.code.status.ErrorStatus; import java.time.LocalDate; import java.util.List; -import java.util.Map; @Service @RequiredArgsConstructor @@ -85,29 +80,59 @@ public void deleteHistory(Long historyId) { @Override @Transactional public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image) { - List cloth = clothRepository.findAllById(historyCreateResult.getClothes()); - if (cloth.isEmpty()) { + + List clothes = clothRepository.findAllById(historyCreateResult.getClothes()); + if (clothes.isEmpty()) { throw new ClothException(ErrorStatus.NO_SUCH_CLOTH); } - History newHistory = History.builder() .historyDate(LocalDate.parse(historyCreateResult.getDate())) .content(historyCreateResult.getContent()) .build(); - HistoryImage newHistoryImage = HistoryImage.builder() + historyRepository.save(newHistory); + + HistoryImage newImage = HistoryImage.builder() .history(newHistory) .imageUrl("url") .build(); + historyImageRepository.save(newImage); - historyImageRepository.save(newHistoryImage); - + for (Cloth c : clothes) { + historyClothRepository.findByHistoryAndCloth(newHistory, c); + } return HistoryConverter.toHistoryCreateResult(newHistory); } @Override - public HistoryResponseDTO.HistoryCreateResult updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image) { - return null; + @Transactional + public void updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image) { + History history = historyRepository.findById(historyId) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + + // 기존 히스토리 정보 갱신 + history.setContent(historyUpdateRequest.getContent()); + history.setHistoryDate(LocalDate.parse(historyUpdateRequest.getDate())); + + // 기존 이미지 삭제 및 새 이미지 저장 (간단화된 예) + historyImageRepository.deleteByHistory(history); + + HistoryImage newImage = HistoryImage.builder() + .history(history) + .imageUrl("newImageUrl") // 실제 구현에서는 image 저장 처리 필요 + .build(); + historyImageRepository.save(newImage); + + // 기존 연결된 옷 정보 갱신 + historyClothRepository.deleteByHistory(history); + List clothes = clothRepository.findAllById(historyUpdateRequest.getClothes()); + if (clothes.isEmpty()) { + throw new ClothException(ErrorStatus.NO_SUCH_CLOTH); + } + + for (Cloth cloth : clothes) { + historyClothRepository.findByHistoryAndCloth(history, cloth); // 커스텀 메서드 필요 + } } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java index 0180b18..16aa8d3 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java @@ -24,6 +24,7 @@ public class History extends BaseEntity { private Long id; @Column(nullable = false) + @Setter private LocalDate historyDate; @Min(0) @@ -31,6 +32,7 @@ public class History extends BaseEntity { private int likes; @Column(length = 200) + @Setter private String content; @ManyToOne(fetch = FetchType.LAZY) diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java index 3c26af5..cd5bd30 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java @@ -8,4 +8,5 @@ public interface HistoryClothRepository extends JpaRepository{ void deleteAllByCloth(Cloth cloth); void deleteByHistory(History history); + void findByHistoryAndCloth(History history, Cloth cloth); } From 0197a9e1e125689b1f8c0ba8f18d6bd313dc3691 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 17:12:13 +0900 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기록 추가 및 수정 api 수정 #41 --- .../application/HistoryServiceImpl.java | 51 +++++++++++++++++-- .../history/converter/HistoryConverter.java | 13 ++--- .../domain/repository/HashtagRepository.java | 3 ++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 1f6207c..160fc74 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -8,12 +8,11 @@ import study.goorm.domain.cloth.domain.repository.ClothRepository; import study.goorm.domain.cloth.exception.ClothException; import study.goorm.domain.history.converter.HistoryConverter; +import study.goorm.domain.history.domain.entity.Hashtag; +import study.goorm.domain.history.domain.entity.HashtagHistory; import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryImage; -import study.goorm.domain.history.domain.repository.HashtagHistoryRepository; -import study.goorm.domain.history.domain.repository.HistoryClothRepository; -import study.goorm.domain.history.domain.repository.HistoryImageRepository; -import study.goorm.domain.history.domain.repository.HistoryRepository; +import study.goorm.domain.history.domain.repository.*; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.history.exception.HistoryExeption; @@ -35,6 +34,7 @@ public class HistoryServiceImpl implements HistoryService { private final HashtagHistoryRepository hashtagHistoryRepository; private final HistoryClothRepository historyClothRepository; private final ClothRepository clothRepository; + private final HashtagRepository hashtagRepository; @Override @@ -102,6 +102,26 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi historyClothRepository.findByHistoryAndCloth(newHistory, c); } + List tags = historyCreateResult.getHashtags(); + + for (String name : tags) { + Hashtag hashtag = hashtagRepository.findByName(name) + .orElseGet(() -> + hashtagRepository.save( + Hashtag.builder() + .name(name) + .build() + ) + ); + + HashtagHistory mapping = HashtagHistory.builder() + .history(newHistory) + .hashtag(hashtag) + .build(); + + hashtagHistoryRepository.save(mapping); + } + return HistoryConverter.toHistoryCreateResult(newHistory); } @@ -134,5 +154,28 @@ public void updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest for (Cloth cloth : clothes) { historyClothRepository.findByHistoryAndCloth(history, cloth); // 커스텀 메서드 필요 } + + // 기존 해시태그 매핑 제거 + hashtagHistoryRepository.deleteByHistory(history); + + // 새 해시태그 등록 + List tags = historyUpdateRequest.getHashtags(); + for (String name : tags) { + Hashtag hashtag = hashtagRepository.findByName(name) + .orElseGet(() -> + hashtagRepository.save( + Hashtag.builder() + .name(name) + .build() + ) + ); + + HashtagHistory mapping = HashtagHistory.builder() + .history(history) + .hashtag(hashtag) + .build(); + + hashtagHistoryRepository.save(mapping); + } } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index 6be65f2..dc7660b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -18,12 +18,6 @@ public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member me .build(); } - public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, HistoryImage historyImage){ - return HistoryResponseDTO.HistoryGetDaily.builder() - - .build(); - } - private static List toHistoryGetList(List histories, List historyImages) { return histories.stream() .map(history -> { @@ -44,6 +38,13 @@ private static HistoryResponseDTO.HistoryGet toHistoryGet(History history, Histo .build(); } + public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, HistoryImage historyImage){ + return HistoryResponseDTO.HistoryGetDaily.builder() + + .build(); + } + + public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history){ return HistoryResponseDTO.HistoryCreateResult.builder() .historyId(history.getId()) diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java index 31330e5..2ac2ce6 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java @@ -3,5 +3,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.Hashtag; +import java.util.Optional; + public interface HashtagRepository extends JpaRepository{ + Optional findByName(String name); } From 06a16d79784494e9b4346a62cbfde02fe79bb46c Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 17:51:37 +0900 Subject: [PATCH 08/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 일의 기록 확인 api 수정 #41 --- .../application/HistoryServiceImpl.java | 20 ++++++++++---- .../history/converter/HistoryConverter.java | 27 +++++++++++++++++-- .../domain/history/domain/entity/History.java | 4 +++ .../repository/HashtagHistoryRepository.java | 4 +++ .../repository/HistoryClothRepository.java | 4 +++ .../repository/HistoryImageRepository.java | 1 + .../history/dto/HistoryResponseDTO.java | 4 ++- 7 files changed, 56 insertions(+), 8 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 160fc74..4c5639d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -8,10 +8,7 @@ import study.goorm.domain.cloth.domain.repository.ClothRepository; import study.goorm.domain.cloth.exception.ClothException; import study.goorm.domain.history.converter.HistoryConverter; -import study.goorm.domain.history.domain.entity.Hashtag; -import study.goorm.domain.history.domain.entity.HashtagHistory; -import study.goorm.domain.history.domain.entity.History; -import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.domain.entity.*; import study.goorm.domain.history.domain.repository.*; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; @@ -59,7 +56,20 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId @Override @Transactional(readOnly = true) public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { - return null; + History history = historyRepository.findById(historyId) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + Member member = history.getMember(); + List images = historyImageRepository.findAllByHistory(history); + List hashtagHistories = hashtagHistoryRepository.findAllByHistory(history); + List hashtags = hashtagHistories.stream() + .map(h -> "#" + h.getHashtag().getName()) + .toList(); + List historyCloths = historyClothRepository.findAllByHistory(history); + List cloths = historyCloths.stream() + .map(HistoryCloth::getCloth) + .toList(); + + return HistoryConverter.toHistoryGetDaily(history,images, member, hashtags, cloths); } @Override diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index dc7660b..703c0be 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -4,9 +4,11 @@ import study.goorm.domain.history.domain.entity.HistoryImage; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.member.domain.entity.Member; +import study.goorm.domain.cloth.domain.entity.Cloth; import java.time.LocalDate; import java.util.List; +import java.util.stream.Collectors; public class HistoryConverter { @@ -38,12 +40,33 @@ private static HistoryResponseDTO.HistoryGet toHistoryGet(History history, Histo .build(); } - public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, HistoryImage historyImage){ + public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, List historyImage, Member member, List hashtags, List cloths){ return HistoryResponseDTO.HistoryGetDaily.builder() - + .memberId(member.getId()) + .historyId(history.getId()) + .memberImageUrl(member.getProfileImageUrl()) + .nickName(member.getNickname()) + .clokeyId(member.getClokeyId()) + .content(history.getContent()) + .images(historyImage) + .hashtags(hashtags) // 해시태그는 외부에서 받아서 설정 + .likeCount(history.getLikes()) // 기본값 또는 실제 값 추가 필요 + .commentCount(history.getComments()) // 기본값 또는 실제 값 추가 필요 + .date(history.getHistoryDate()) + .clothes(toHistoryDailyList(cloths)) // 실제 의류 정보 변환 + .liked(false) // 실제 좋아요 여부는 사용자 컨텍스트 필요 .build(); } + private static List toHistoryDailyList(List cloths) { + return cloths.stream() + .map(cloth -> HistoryResponseDTO.HistoryGetCloth.builder() + .clothId(cloth.getId()) + .clothImageUrl(cloth.getClothUrl()) + .clothName(cloth.getName()) + .build()) + .collect(Collectors.toList()); + } public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history){ return HistoryResponseDTO.HistoryCreateResult.builder() diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java index 16aa8d3..9b8d6a9 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java @@ -31,6 +31,10 @@ public class History extends BaseEntity { @Column(nullable = false, columnDefinition = "integer default 0") private int likes; + @Min(0) + @Column(nullable = false, columnDefinition = "integer default 0") + private int comments; + @Column(length = 200) @Setter private String content; diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java index 476ed5c..c44f8a4 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java @@ -3,7 +3,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.HashtagHistory; import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.domain.entity.HistoryImage; + +import java.util.List; public interface HashtagHistoryRepository extends JpaRepository{ void deleteByHistory(History history); + List findAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java index cd5bd30..b17a0b9 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java @@ -2,11 +2,15 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.cloth.domain.entity.Cloth; +import study.goorm.domain.history.domain.entity.HashtagHistory; import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryCloth; +import java.util.List; + public interface HistoryClothRepository extends JpaRepository{ void deleteAllByCloth(Cloth cloth); void deleteByHistory(History history); void findByHistoryAndCloth(History history, Cloth cloth); + List findAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java index dae5c91..8eed501 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java @@ -8,5 +8,6 @@ public interface HistoryImageRepository extends JpaRepository{ List findAllByHistoryIdIn(List historyIds); + List findAllByHistory(History history); void deleteByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 41a659b..0c96672 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import study.goorm.domain.history.domain.entity.HistoryImage; import java.time.LocalDate; import java.util.List; @@ -38,9 +39,10 @@ public static class HistoryGetDaily { private String nickName; private String clokeyId; private String content; - private List images; + private List images; private List hashtags; private int likeCount; + private int commentCount; private boolean liked; private LocalDate date; private List clothes; From 1fdd531218e2803c059583435549c2572b8b3cd2 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 28 May 2025 18:30:50 +0900 Subject: [PATCH 09/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 월별 기록 첫화면 사진 지정 #41 --- .../goorm/domain/history/application/HistoryServiceImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 4c5639d..d9ac0e9 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -20,12 +20,14 @@ import java.time.LocalDate; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor public class HistoryServiceImpl implements HistoryService { private final HistoryRepository historyRepository; + private final HistoryImageQueryService historyImageQueryService; private final HistoryImageRepository historyImageRepository; private final MemberRepository memberRepository; private final HashtagHistoryRepository hashtagHistoryRepository; @@ -50,6 +52,8 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId .toList(); List historyImages = historyImageRepository.findAllByHistoryIdIn(historyIds); + Map firstImagesOfHistory = historyImageQueryService.getFirstHistoryImageUrlMap(histories); + return HistoryConverter.toHistoryGetMonthly(member, month, histories, historyImages); } From 1c180defb80cce320e75f54f3a313df911fcf02b Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:42:31 +0900 Subject: [PATCH 10/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 8주차 과제_history api 수정 마무리하기 #41 --- .../domain/repository/ClothRepository.java | 2 + .../history/api/HistoryRestController.java | 17 +- .../history/application/HistoryService.java | 7 +- .../application/HistoryServiceImpl.java | 299 ++++++++++++------ .../history/converter/HistoryConverter.java | 47 +-- .../domain/history/domain/entity/History.java | 4 + .../domain/repository/CommentRepository.java | 1 + .../repository/HashtagHistoryRepository.java | 3 +- .../domain/repository/HashtagRepository.java | 4 +- .../repository/HistoryClothRepository.java | 7 +- .../repository/HistoryImageRepository.java | 3 +- .../domain/repository/HistoryRepository.java | 9 +- .../domain/history/dto/HistoryRequestDTO.java | 21 +- .../history/dto/HistoryResponseDTO.java | 18 +- .../goorm/global/config/SwaggerConfig.java | 13 +- .../global/error/code/status/ErrorStatus.java | 4 +- .../error/code/status/SuccessStatus.java | 2 +- 17 files changed, 305 insertions(+), 156 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java b/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java index 5546f40..21c1fb2 100644 --- a/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/cloth/domain/repository/ClothRepository.java @@ -21,4 +21,6 @@ public interface ClothRepository extends JpaRepository{ // 4. createdAt 내림차순 Page findByMemberOrderByCreatedAtDesc(Member member, Pageable pageable); + List findByMemberId(Long memberId); + } diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index d5e263b..3a3286a 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -23,6 +23,7 @@ import study.goorm.domain.history.application.HistoryService; import java.time.LocalDate; +import java.util.List; @RestController @RequiredArgsConstructor @@ -31,6 +32,7 @@ public class HistoryRestController { private final HistoryService historyService; + // 월별 기록 조회 @GetMapping("/monthly") @Operation(summary = "특정 회원의 월별 기록을 조회하는 API", description = "query string으로 clokeyId, month를 넘겨주세요.") @ApiResponses({ @@ -42,13 +44,14 @@ public class HistoryRestController { }) public BaseResponse getMonthlyHistories( @RequestParam(value = "clokeyId") String clokeyId, - @RequestParam LocalDate month + @RequestParam String month ) { HistoryResponseDTO.HistoryGetMonthly result = historyService.getHistoryGetMonthly(clokeyId, month); return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); } + // 일별 기록 조회 @GetMapping("/{historyId}") @Operation(summary = "특정 회원의 특정 일의 기록을 확인할 수 있는 API", description = "path variable로 historyId를 넘겨주세요.") @ApiResponses({ @@ -65,6 +68,7 @@ public BaseResponse getDailyHistory( return BaseResponse.onSuccess(SuccessStatus.HISTORY_GET_MONTH, result); } + // 기록 삭제 @DeleteMapping("/{historyId}") @Operation(summary = "특정 기록을 삭제하는 API", description = "path variable로 historyId를 넘겨주세요.") @ApiResponses({ @@ -82,6 +86,7 @@ public BaseResponse deleteHistory( return BaseResponse.onSuccess(SuccessStatus.HISTORY_DELETED, null); } + // 옷 기록 추가 @PostMapping(value = "", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "새로운 옷 기록을 생성하는 API", description = "request body에 HistoryCreateRequest 형식의 데이터를 전달해주세요.") @ApiResponses({ @@ -89,7 +94,7 @@ public BaseResponse deleteHistory( }) public BaseResponse createHistory( @RequestPart("historyCreateRequest") HistoryRequestDTO.HistoryCreateRequest historyCreateRequest, - @RequestPart("imageFile") MultipartFile imageFile + @RequestPart("imageFile") List imageFile ) { HistoryResponseDTO.HistoryCreateResult result = historyService.createHistory(historyCreateRequest,imageFile); @@ -99,17 +104,17 @@ public BaseResponse createHistory( @PatchMapping(value = "/{historyId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Operation(summary = "특정날짜 옷 기록을 수정하는 API", description = "path variable로 historyId를 넘겨주세요.") @ApiResponses({ - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_203", description = "OK, 기록이 성공적으로 수정되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "OK, 기록이 성공적으로 수정되었습니다."), }) @Parameters({ @Parameter(name = "historyId", description = "기록의 id, path variable 입니다."), }) public BaseResponse patchHistory( @PathVariable Long historyId, - @RequestPart("historyUpdateRequest") HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, - @RequestPart(value = "imageFile", required = false) MultipartFile imageFile + @RequestPart("historyUpdateRequest") @Valid HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, + @RequestPart("imageFile") List imageFile ) { - historyService.updateHistory(historyId, historyUpdateRequest, imageFile); + HistoryResponseDTO.HistoryUpdateResult result = historyService.updateHistory(historyUpdateRequest, imageFile, historyId); return BaseResponse.onSuccess(SuccessStatus.HISTORY_UPDATED, null); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index 94fddc2..c67f30e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -6,11 +6,12 @@ import study.goorm.domain.member.domain.entity.Member; import java.time.LocalDate; +import java.util.List; public interface HistoryService { - HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month); + HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, String month); HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId); + HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, List image); + HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, List imageFile, Long historyId); void deleteHistory(Long historyId); - HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image); - void updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index d9ac0e9..f022ec2 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -18,9 +18,14 @@ import study.goorm.domain.member.domain.repository.MemberRepository; import study.goorm.global.error.code.status.ErrorStatus; +import java.time.DateTimeException; import java.time.LocalDate; +import java.time.YearMonth; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -34,27 +39,60 @@ public class HistoryServiceImpl implements HistoryService { private final HistoryClothRepository historyClothRepository; private final ClothRepository clothRepository; private final HashtagRepository hashtagRepository; + private final CommentRepository commentRepository; @Override @Transactional(readOnly = true) - public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, LocalDate month) { - Member member = memberRepository.findByClokeyId(clokeyId) - .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId, String month) { + // 사용자 정보 => 변경 사항 + Member member; + // clokeyId가 null인 경우 + if (clokeyId == null) { + member = memberRepository.findByClokeyId("1") + .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + } else { // clokeyId가 있는 경우 + member = memberRepository.findByClokeyId(clokeyId) + .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + } + + // Month 형식 검사 + try { + YearMonth.parse(month); + } catch (DateTimeException e) { + e.printStackTrace(); + } - // 해당 월의 시작일과 마지막일 계산 - LocalDate startOfMonth = month.withDayOfMonth(1); - LocalDate endOfMonth = month.withDayOfMonth(month.lengthOfMonth()); + // 기록 조회 + List histories = historyRepository.findHistoriesByMemberIdAndYearMonth(member.getId(), month); - List histories = historyRepository.findAllByMemberAndHistoryDateBetween(member, startOfMonth, endOfMonth); List historyIds = histories.stream() .map(History::getId) - .toList(); - List historyImages = historyImageRepository.findAllByHistoryIdIn(historyIds); +// .toList(); + .collect(Collectors.toList()); // => toList와의 차이점은? - Map firstImagesOfHistory = historyImageQueryService.getFirstHistoryImageUrlMap(histories); + // 사진 조회 + List historyImages = historyImageRepository.findAllByHistoryIdIn(historyIds); - return HistoryConverter.toHistoryGetMonthly(member, month, histories, historyImages); + // 첫번째 이미지 + Map firstImagesOfHistory = historyImages.stream() + .collect(Collectors.groupingBy( + img -> img.getHistory().getId(), + Collectors.mapping(HistoryImage::getImageUrl, Collectors.collectingAndThen( + Collectors.toList(), + list -> list.isEmpty() ? "private" : list.get(0) + )) + )); + + List resultList = histories.stream() + .map(history -> HistoryResponseDTO.HistoryGetMonthlyResult.builder() + .historyId(history.getId()) + .date(LocalDate.parse(history.getHistoryDate().toString())) + .imageUrl(firstImagesOfHistory.getOrDefault(history.getId(), "비공개입니다")) + .build()) + .collect(Collectors.toList()); + + return HistoryConverter.toHistoryGetMonthly(member, resultList); } @Override @@ -62,18 +100,40 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { History history = historyRepository.findById(historyId) .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + Member member = history.getMember(); - List images = historyImageRepository.findAllByHistory(history); - List hashtagHistories = hashtagHistoryRepository.findAllByHistory(history); - List hashtags = hashtagHistories.stream() - .map(h -> "#" + h.getHashtag().getName()) + + // 이미지 조회 + List historyImages = historyImageRepository.findAllByHistory(history); + List images = historyImages.stream() + .map(HistoryImage::getImageUrl) + .toList(); + + // 해시태그 조회 + List hashtagHistories = hashtagHistoryRepository.findByHistoryId(historyId); + + // 기록의 해시태그 조회 + List hashtags = hashtagHistories.stream() + .map(HashtagHistory::getHashtag) + .toList(); + List hashTagName = hashtags.stream() + .map(Hashtag::getName) .toList(); - List historyCloths = historyClothRepository.findAllByHistory(history); - List cloths = historyCloths.stream() - .map(HistoryCloth::getCloth) + + // 댓글 갯수 세기 => 이건 생각 못함 + long commentCount = commentRepository.countByHistoryId(historyId); + + List cloths = clothRepository.findByMemberId(member.getId()); + + List clothList = cloths.stream() + .map(cloth -> HistoryResponseDTO.HistoryGetDailyCloth.builder() + .clothId(cloth.getId()) + .clothImageUrl(cloth.getClothUrl()) // 이미지 필드 맞게 수정 + .clothName(cloth.getName()) + .build()) .toList(); - return HistoryConverter.toHistoryGetDaily(history,images, member, hashtags, cloths); + return HistoryConverter.toHistoryGetDaily(history, images, member, hashTagName, clothList); } @Override @@ -83,9 +143,9 @@ public void deleteHistory(Long historyId) { .orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); //매핑 테이블 삭제 - hashtagHistoryRepository.deleteByHistory(history); - historyImageRepository.deleteByHistory(history); - historyClothRepository.deleteByHistory(history); + hashtagHistoryRepository.deleteByHistory(history); // 해시태그 삭제 + historyImageRepository.deleteAllByHistory(history); // 이미지 삭제 + historyClothRepository.deleteByHistory(history); // 옷 매핑 삭제 //최종 옷 삭제 historyRepository.delete(history); @@ -93,103 +153,150 @@ public void deleteHistory(Long historyId) { @Override @Transactional - public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, MultipartFile image) { - - List clothes = clothRepository.findAllById(historyCreateResult.getClothes()); - if (clothes.isEmpty()) { - throw new ClothException(ErrorStatus.NO_SUCH_CLOTH); + public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, List image) { + // 이미지 업로드 개수 제한 + if (image.size() >= 10) { + throw new HistoryExeption(ErrorStatus.TOO_MANY_IMAGES); } - History newHistory = History.builder() - .historyDate(LocalDate.parse(historyCreateResult.getDate())) + // member 1번이 로그인 한 유저라고 가정 + Member member = memberRepository.findById(1L) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_MEMBER)); + + // history 테이블에 내용, 날짜 저장 + History history = History.builder() + .member(member) .content(historyCreateResult.getContent()) + .historyDate(historyCreateResult.getDate()) .build(); - historyRepository.save(newHistory); - HistoryImage newImage = HistoryImage.builder() - .history(newHistory) - .imageUrl("url") - .build(); - historyImageRepository.save(newImage); + historyRepository.save(history); - for (Cloth c : clothes) { - historyClothRepository.findByHistoryAndCloth(newHistory, c); + // clothId 검증 + List requestedIds = historyCreateResult.getClothes(); + + for (Long id : requestedIds) { + boolean exists = clothRepository.existsById(id); + if (!exists) { + throw new HistoryExeption(ErrorStatus.NO_SUCH_CLOTH); + } } - List tags = historyCreateResult.getHashtags(); + // ClothId 기록 + List clothes = clothRepository.findAllById(requestedIds); - for (String name : tags) { - Hashtag hashtag = hashtagRepository.findByName(name) - .orElseGet(() -> - hashtagRepository.save( - Hashtag.builder() - .name(name) - .build() - ) - ); + List historyClothes = clothes.stream() + .map(cloth -> HistoryCloth.builder() + .history(history) + .cloth(cloth) + .build()) + .toList(); - HashtagHistory mapping = HashtagHistory.builder() - .history(newHistory) - .hashtag(hashtag) - .build(); + historyClothRepository.saveAll(historyClothes); - hashtagHistoryRepository.save(mapping); - } + List requestedTags = historyCreateResult.getHashtags(); + + // DB에 존재하는 해시태그 조회 + List existingHashtags = hashtagRepository.findAllByNameIn(requestedTags); + Set existingTagNames = existingHashtags.stream() + .map(Hashtag::getName) + .collect(Collectors.toSet()); + + // 없는 해시태그 추출 + List newHashtags = requestedTags.stream() + .filter(tag -> !existingTagNames.contains(tag)) + .map(tag -> Hashtag.builder().name(tag).build()) + .toList(); - return HistoryConverter.toHistoryCreateResult(newHistory); + // 새 해시태그 저장 + hashtagRepository.saveAll(newHashtags); + + // 기존 + 신규 해시태그 합치기 + List allHashtags = new ArrayList<>(); + allHashtags.addAll(existingHashtags); + allHashtags.addAll(newHashtags); + + // HashtagHistory 저장 + List hashtagHistories = allHashtags.stream() + .map(tag -> HashtagHistory.builder() + .hashtag(tag) + .history(history) + .build()) + .toList(); + hashtagHistoryRepository.saveAll(hashtagHistories); + + return HistoryConverter.toHistoryCreateResult(history); } @Override @Transactional - public void updateHistory(Long historyId, HistoryRequestDTO.HistoryCreateRequest historyUpdateRequest, MultipartFile image) { + public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, List images, Long historyId) { + if (images.size() >= 10) { + throw new HistoryExeption(ErrorStatus.TOO_MANY_IMAGES); + } + + Member member = memberRepository.findById(1L) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_MEMBER)); + + // 기존 히스토리 조회 History history = historyRepository.findById(historyId) .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); - // 기존 히스토리 정보 갱신 - history.setContent(historyUpdateRequest.getContent()); - history.setHistoryDate(LocalDate.parse(historyUpdateRequest.getDate())); + // content & visibility 수정 + history.update(historyUpdateRequest.getContent()); - // 기존 이미지 삭제 및 새 이미지 저장 (간단화된 예) - historyImageRepository.deleteByHistory(history); + // 기존 clothes & hashtag 관계 삭제 + historyClothRepository.deleteByHistory(history); // custom deleteByHistory + hashtagHistoryRepository.deleteByHistory(history); // custom deleteByHistory - HistoryImage newImage = HistoryImage.builder() - .history(history) - .imageUrl("newImageUrl") // 실제 구현에서는 image 저장 처리 필요 - .build(); - historyImageRepository.save(newImage); + // 새로운 clothes 저장 + List clothIds = historyUpdateRequest.getClothes(); + List clothes = clothRepository.findAllById(clothIds); - // 기존 연결된 옷 정보 갱신 - historyClothRepository.deleteByHistory(history); - List clothes = clothRepository.findAllById(historyUpdateRequest.getClothes()); - if (clothes.isEmpty()) { - throw new ClothException(ErrorStatus.NO_SUCH_CLOTH); - } + List historyClothes = clothes.stream() + .map(cloth -> HistoryCloth.builder() + .history(history) + .cloth(cloth) + .build()) + .toList(); + historyClothRepository.saveAll(historyClothes); - for (Cloth cloth : clothes) { - historyClothRepository.findByHistoryAndCloth(history, cloth); // 커스텀 메서드 필요 - } + // 새로운 해시태그 저장 (새로운 해시태그 생성 포함) + List tagNames = historyUpdateRequest.getHashtags(); - // 기존 해시태그 매핑 제거 - hashtagHistoryRepository.deleteByHistory(history); - - // 새 해시태그 등록 - List tags = historyUpdateRequest.getHashtags(); - for (String name : tags) { - Hashtag hashtag = hashtagRepository.findByName(name) - .orElseGet(() -> - hashtagRepository.save( - Hashtag.builder() - .name(name) - .build() - ) - ); - - HashtagHistory mapping = HashtagHistory.builder() - .history(history) - .hashtag(hashtag) - .build(); - - hashtagHistoryRepository.save(mapping); - } + List existingTags = hashtagRepository.findAllByNameIn(tagNames); + Set existingTagNames = existingTags.stream() + .map(Hashtag::getName) + .collect(Collectors.toSet()); + + List newTags = tagNames.stream() + .filter(name -> !existingTagNames.contains(name)) + .map(name -> Hashtag.builder().name(name).build()) + .toList(); + + hashtagRepository.saveAll(newTags); + + List allTags = new ArrayList<>(); + allTags.addAll(existingTags); + allTags.addAll(newTags); + + List hashtagHistories = allTags.stream() + .map(tag -> HashtagHistory.builder() + .hashtag(tag) + .history(history) + .build()) + .toList(); + + hashtagHistoryRepository.saveAll(hashtagHistories); + + // 기존 이미지 삭제 + List existingImages = historyImageRepository.findAllByHistory(history); + + + // DB에서 HistoryImage 삭제 + historyImageRepository.deleteAllByHistory(history); + + + return HistoryConverter.toHistoryUpdateResult(history); } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index 703c0be..f740d7e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -12,35 +12,16 @@ public class HistoryConverter { - public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member member, LocalDate month, List histories, List historyImages){ + public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member member, List result) { + return HistoryResponseDTO.HistoryGetMonthly.builder() .memberId(member.getId()) .nickName(member.getNickname()) - .histories(toHistoryGetList(histories, historyImages)) - .build(); - } - - private static List toHistoryGetList(List histories, List historyImages) { - return histories.stream() - .map(history -> { - HistoryImage image = historyImages.stream() - .filter(img -> img.getHistory().getId().equals(history.getId())) - .findFirst() - .orElse(null); - return toHistoryGet(history, image); - }) - .collect(java.util.stream.Collectors.toList()); - } - - private static HistoryResponseDTO.HistoryGet toHistoryGet(History history, HistoryImage historyImage){ - return HistoryResponseDTO.HistoryGet.builder() - .historyId(history.getId()) - .date(history.getHistoryDate()) - .imageUrl(historyImage != null ? historyImage.getImageUrl() : "비공개입니다") + .histories(result) .build(); } - public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, List historyImage, Member member, List hashtags, List cloths){ + public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, List images, Member member, List hashtags, List cloths){ return HistoryResponseDTO.HistoryGetDaily.builder() .memberId(member.getId()) .historyId(history.getId()) @@ -48,29 +29,25 @@ public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History histo .nickName(member.getNickname()) .clokeyId(member.getClokeyId()) .content(history.getContent()) - .images(historyImage) + .imageUrl(images) .hashtags(hashtags) // 해시태그는 외부에서 받아서 설정 .likeCount(history.getLikes()) // 기본값 또는 실제 값 추가 필요 .commentCount(history.getComments()) // 기본값 또는 실제 값 추가 필요 .date(history.getHistoryDate()) - .clothes(toHistoryDailyList(cloths)) // 실제 의류 정보 변환 + .cloths(cloths) // 실제 의류 정보 변환 .liked(false) // 실제 좋아요 여부는 사용자 컨텍스트 필요 .build(); } - private static List toHistoryDailyList(List cloths) { - return cloths.stream() - .map(cloth -> HistoryResponseDTO.HistoryGetCloth.builder() - .clothId(cloth.getId()) - .clothImageUrl(cloth.getClothUrl()) - .clothName(cloth.getName()) - .build()) - .collect(Collectors.toList()); - } - public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history){ return HistoryResponseDTO.HistoryCreateResult.builder() .historyId(history.getId()) .build(); } + + public static HistoryResponseDTO.HistoryUpdateResult toHistoryUpdateResult(History history){ + return HistoryResponseDTO.HistoryUpdateResult.builder() + .historyId(history.getId()) + .build(); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java index 9b8d6a9..fe25f8d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java @@ -43,5 +43,9 @@ public class History extends BaseEntity { @JoinColumn(name = "member_id", nullable = false) private Member member; + // update 메서드 추가 + public void update(String content) { + this.content = content; + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java index c37745a..9676406 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java @@ -4,4 +4,5 @@ import study.goorm.domain.history.domain.entity.Comment; public interface CommentRepository extends JpaRepository{ + long countByHistoryId(Long historyId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java index c44f8a4..5f0b9b5 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagHistoryRepository.java @@ -8,6 +8,7 @@ import java.util.List; public interface HashtagHistoryRepository extends JpaRepository{ + List findByHistoryId(Long historyId); void deleteByHistory(History history); - List findAllByHistory(History history); + void deleteAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java index 2ac2ce6..34a7125 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HashtagRepository.java @@ -3,8 +3,10 @@ import org.springframework.data.jpa.repository.JpaRepository; import study.goorm.domain.history.domain.entity.Hashtag; +import java.util.List; import java.util.Optional; public interface HashtagRepository extends JpaRepository{ - Optional findByName(String name); + List findAllByNameIn(List names); + boolean existsByName(String name); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java index b17a0b9..8d61129 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java @@ -7,10 +7,13 @@ import study.goorm.domain.history.domain.entity.HistoryCloth; import java.util.List; +import java.util.Optional; public interface HistoryClothRepository extends JpaRepository{ + // Cloth void deleteAllByCloth(Cloth cloth); + + // History void deleteByHistory(History history); - void findByHistoryAndCloth(History history, Cloth cloth); - List findAllByHistory(History history); + void deleteAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java index 8eed501..da03965 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryImageRepository.java @@ -8,6 +8,7 @@ public interface HistoryImageRepository extends JpaRepository{ List findAllByHistoryIdIn(List historyIds); + List findByHistoryId(Long historyId); List findAllByHistory(History history); - void deleteByHistory(History history); + void deleteAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java index 5781c5b..6b23ef6 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java @@ -1,6 +1,8 @@ package study.goorm.domain.history.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.member.domain.entity.Member; @@ -8,5 +10,10 @@ import java.util.List; public interface HistoryRepository extends JpaRepository{ - List findAllByMemberAndHistoryDateBetween(Member member, LocalDate start, LocalDate end); + // 이렇게도 쓸 수 있구나! + @Query("SELECT h FROM History h " + + "WHERE h.member.id = :memberId AND FUNCTION('DATE_FORMAT', h.historyDate, '%Y-%m') = :yearMonth") + List findHistoriesByMemberIdAndYearMonth(@Param("memberId") Long memberId, @Param("yearMonth") String yearMonth); + + History findById(long historyId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java index 0869a9d..a7a5c25 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java @@ -1,5 +1,6 @@ package study.goorm.domain.history.dto; +import jakarta.persistence.Column; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import lombok.AllArgsConstructor; @@ -10,6 +11,7 @@ import study.goorm.domain.model.enums.Season; import study.goorm.domain.model.enums.ThicknessLevel; +import java.time.LocalDate; import java.util.List; public class HistoryRequestDTO { @@ -20,12 +22,29 @@ public class HistoryRequestDTO { @AllArgsConstructor public static class HistoryCreateRequest { + @Column(length = 200) private String content; private List clothes; private List hashtags; - private String date; + private LocalDate date; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryUpdateRequest { + + @Column(length = 200) + private String content; + + private List clothes; + + private List hashtags; + +// private Visibility visibility; } } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 0c96672..466c6e4 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -17,13 +17,13 @@ public class HistoryResponseDTO { public static class HistoryGetMonthly { private Long memberId; private String nickName; - private List histories; + private List histories; } @Builder @Getter @NoArgsConstructor @AllArgsConstructor - public static class HistoryGet { + public static class HistoryGetMonthlyResult { private Long historyId; private LocalDate date; private String imageUrl; @@ -39,13 +39,13 @@ public static class HistoryGetDaily { private String nickName; private String clokeyId; private String content; - private List images; + private List imageUrl; private List hashtags; private int likeCount; private int commentCount; private boolean liked; private LocalDate date; - private List clothes; + private List cloths; private long clothId; private long historyId; } @@ -53,7 +53,7 @@ public static class HistoryGetDaily { @Getter @NoArgsConstructor @AllArgsConstructor - public static class HistoryGetCloth { + public static class HistoryGetDailyCloth { private Long clothId; private String clothImageUrl; private String clothName; @@ -66,4 +66,12 @@ public static class HistoryGetCloth { public static class HistoryCreateResult { private Long historyId; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryUpdateResult { + private Long historyId; + } } diff --git a/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java b/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java index 2dfd637..faac566 100644 --- a/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java +++ b/goorm/src/main/java/study/goorm/global/config/SwaggerConfig.java @@ -19,6 +19,17 @@ @Configuration public class SwaggerConfig { + /** + * Multipart + JSON 동시에 Swagger에서 테스트할 때 + * content-type null → application/octet-stream 으로 처리되어 Jackson에서 인식 못하는 문제를 해결 + */ + @Autowired + public void configureMessageConverter(MappingJackson2HttpMessageConverter converter) { + List supportMediaTypes = new ArrayList<>(converter.getSupportedMediaTypes()); + supportMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); + converter.setSupportedMediaTypes(supportMediaTypes); + } + @Bean public OpenAPI goormStudyAPI() { Info info = new Info() @@ -41,4 +52,4 @@ public OpenAPI goormStudyAPI() { .addSecurityItem(securityRequirement) .components(components); } -} \ No newline at end of file +} diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index 20985e1..269ccec 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -31,8 +31,8 @@ public enum ErrorStatus implements BaseErrorCode { LOWER_TEMP_BIGGER_THAN_UPPER_TEMP(HttpStatus.BAD_REQUEST,"CLOTH_4004","옷의 하한 온도가 상한 온도 보다 높습니다."), // History - NO_SUCH_HISTORY(HttpStatus.BAD_REQUEST, "HISTORY_4002","존재하지 않는 기록 ID 입니다.") - ; + NO_SUCH_HISTORY(HttpStatus.BAD_REQUEST, "HISTORY_4002","존재하지 않는 기록 ID 입니다."), + TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"); private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index 6b75704..88694c7 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -23,7 +23,7 @@ public enum SuccessStatus implements BaseCode { HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."), HISTORY_CREATED(HttpStatus.CREATED,"HISTORY_201","옷 기록이 성공적으로 생성되었습니다."), HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."), - HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_203","성공적으로 수정되었습니다"); + HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_200","성공적으로 수정되었습니다"); private final HttpStatus httpStatus; private final String code; From 322c9d0cf2818f5e43383aa0afabf927815d3aef Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:45:33 +0900 Subject: [PATCH 11/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 8주차 과제 수정 #41 --- .../goorm/domain/history/application/HistoryServiceImpl.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index f022ec2..fdd5f68 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -120,9 +120,6 @@ public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { .map(Hashtag::getName) .toList(); - // 댓글 갯수 세기 => 이건 생각 못함 - long commentCount = commentRepository.countByHistoryId(historyId); - List cloths = clothRepository.findByMemberId(member.getId()); List clothList = cloths.stream() From 63c225b68981fea27098a6c44d87c4c26033e470 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:33:31 +0900 Subject: [PATCH 12/21] =?UTF-8?q?fix:=20history=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 8주차 api 수정 과제 마무리 #41 --- .../domain/cloth/domain/entity/Cloth.java | 6 ++ .../application/HistoryServiceImpl.java | 75 ++++++++++++++----- .../domain/repository/CommentRepository.java | 7 ++ .../repository/HistoryClothRepository.java | 1 + .../repository/MemberLikeRepository.java | 7 ++ .../global/error/code/status/ErrorStatus.java | 7 +- 6 files changed, 84 insertions(+), 19 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/cloth/domain/entity/Cloth.java b/goorm/src/main/java/study/goorm/domain/cloth/domain/entity/Cloth.java index dce955c..f83fb76 100644 --- a/goorm/src/main/java/study/goorm/domain/cloth/domain/entity/Cloth.java +++ b/goorm/src/main/java/study/goorm/domain/cloth/domain/entity/Cloth.java @@ -64,4 +64,10 @@ public class Cloth extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id", nullable = false) private Member member; + + @Min(0) + private int wearCount; + public void decreaseWearCount() { + if (this.wearCount > 0) this.wearCount--; + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index fdd5f68..e799637 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -18,13 +18,12 @@ import study.goorm.domain.member.domain.repository.MemberRepository; import study.goorm.global.error.code.status.ErrorStatus; +import java.io.File; +import java.io.IOException; import java.time.DateTimeException; import java.time.LocalDate; import java.time.YearMonth; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; @Service @@ -40,6 +39,7 @@ public class HistoryServiceImpl implements HistoryService { private final ClothRepository clothRepository; private final HashtagRepository hashtagRepository; private final CommentRepository commentRepository; + private final MemberLikeRepository memberLikeRepository; @Override @@ -60,7 +60,7 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId try { YearMonth.parse(month); } catch (DateTimeException e) { - e.printStackTrace(); + throw new HistoryExeption(ErrorStatus.INVALID_DATE_FORMAT); } // 기록 조회 @@ -80,7 +80,7 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId img -> img.getHistory().getId(), Collectors.mapping(HistoryImage::getImageUrl, Collectors.collectingAndThen( Collectors.toList(), - list -> list.isEmpty() ? "private" : list.get(0) + list -> list.isEmpty() ? "null" : list.get(0) )) )); @@ -136,15 +136,33 @@ public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { @Override @Transactional public void deleteHistory(Long historyId) { + // History 조회 History history = historyRepository.findById(historyId) - .orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + + // 댓글 전부 삭제 + commentRepository.deleteByHistory(history); + + // 좋아요 전부 삭제 + memberLikeRepository.deleteByHistory(history); + + // 옷 착용 횟수 감소 + List historyClothes = historyClothRepository.findByHistory(history); + for (HistoryCloth hc : historyClothes) { + Cloth cloth = hc.getCloth(); + cloth.decreaseWearCount(); + } - //매핑 테이블 삭제 - hashtagHistoryRepository.deleteByHistory(history); // 해시태그 삭제 - historyImageRepository.deleteAllByHistory(history); // 이미지 삭제 - historyClothRepository.deleteByHistory(history); // 옷 매핑 삭제 + // HashtagHistory 삭제 + hashtagHistoryRepository.deleteByHistory(history); - //최종 옷 삭제 + // 이미지 삭제 + historyImageRepository.deleteAllByHistory(history); + + // History-Cloth 매핑 row 삭제 + historyClothRepository.deleteByHistory(history); + + // 최종 History 삭제 historyRepository.delete(history); } @@ -239,7 +257,7 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi History history = historyRepository.findById(historyId) .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); - // content & visibility 수정 + // content 수정 history.update(historyUpdateRequest.getContent()); // 기존 clothes & hashtag 관계 삭제 @@ -286,13 +304,36 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi hashtagHistoryRepository.saveAll(hashtagHistories); - // 기존 이미지 삭제 - List existingImages = historyImageRepository.findAllByHistory(history); - - // DB에서 HistoryImage 삭제 historyImageRepository.deleteAllByHistory(history); + String uploadDir = "history/"; // 상대 경로 또는 절대 경로 + List newHistoryImages = new ArrayList<>(); + + for (MultipartFile file : images) { + if (file.isEmpty()) continue; + + String originalFilename = file.getOriginalFilename(); + String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + String uniqueName = UUID.randomUUID().toString() + extension; + + File dest = new File(uploadDir + uniqueName); + try { + file.transferTo(dest); // 실제 파일 저장 + } catch (IOException e) { + throw new RuntimeException("이미지 저장 실패", e); + } + + String imageUrl = "/uploads/history/" + uniqueName; // 클라이언트 접근 경로 + HistoryImage historyImage = HistoryImage.builder() + .history(history) + .imageUrl(imageUrl) + .build(); + + newHistoryImages.add(historyImage); + } + + historyImageRepository.saveAll(newHistoryImages); return HistoryConverter.toHistoryUpdateResult(history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java index 9676406..5d48f12 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java @@ -1,8 +1,15 @@ package study.goorm.domain.history.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import study.goorm.domain.history.domain.entity.Comment; +import study.goorm.domain.history.domain.entity.History; public interface CommentRepository extends JpaRepository{ long countByHistoryId(Long historyId); + @Modifying + @Query("DELETE FROM Comment c WHERE c.history = :history") + void deleteByHistory(@Param("history") History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java index 8d61129..576fcd9 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryClothRepository.java @@ -14,6 +14,7 @@ public interface HistoryClothRepository extends JpaRepository findByHistory(History history); void deleteByHistory(History history); void deleteAllByHistory(History history); } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java index 2fb6291..3956d1d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java @@ -1,7 +1,14 @@ package study.goorm.domain.history.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.MemberLike; public interface MemberLikeRepository extends JpaRepository{ + @Modifying + @Query("DELETE FROM MemberLike l WHERE l.history = :history") + void deleteByHistory(@Param("history") History history); } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index 269ccec..000b36f 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -20,7 +20,7 @@ public enum ErrorStatus implements BaseErrorCode { NO_SUCH_CLOTH(HttpStatus.BAD_REQUEST, "CLOTH_4001","옷이 존재하지 않습니다"), NO_ClOTH_IMAGE(HttpStatus.BAD_REQUEST,"CLOTH_4002","옷의 사진이 존재하지 않습니다."), // Member - NO_SUCH_MEMBER(HttpStatus.BAD_REQUEST,"MEMBER_4001","멤버가 존재하지 않습니다."), + NO_SUCH_MEMBER(HttpStatus.BAD_REQUEST,"MEMBER_4003","멤버가 존재하지 않습니다."), // Page PAGE_UNDER_ONE(HttpStatus.BAD_REQUEST,"PAGE_4001","페이지는 1이상으로 입력해야 합니다."), @@ -31,8 +31,11 @@ public enum ErrorStatus implements BaseErrorCode { LOWER_TEMP_BIGGER_THAN_UPPER_TEMP(HttpStatus.BAD_REQUEST,"CLOTH_4004","옷의 하한 온도가 상한 온도 보다 높습니다."), // History + INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, "HISTORY_4001", "잘못된 날짜 형식입니다."), NO_SUCH_HISTORY(HttpStatus.BAD_REQUEST, "HISTORY_4002","존재하지 않는 기록 ID 입니다."), - TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"); + TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"), + NO_PERMISSION_FOR_RECORD(HttpStatus.BAD_REQUEST, "HISTORY_4006","기록에 접근 권한이 없습니다.") + ; private final HttpStatus httpStatus; private final String code; private final String message; From 00b52934a63b30f589c5da4714bde058f6572fe2 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:38:16 +0900 Subject: [PATCH 13/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - S3 추가 #41 --- .gitignore | 3 +++ goorm/build.gradle | 1 + .../domain/history/application/HistoryServiceImpl.java | 3 +++ goorm/src/main/resources/application-aws.yml | 9 +++++++++ goorm/src/main/resources/application.yml | 2 ++ 5 files changed, 18 insertions(+) create mode 100644 goorm/src/main/resources/application-aws.yml diff --git a/.gitignore b/.gitignore index c2065bc..7f02808 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ out/ ### VS Code ### .vscode/ + +application-aws.yml + diff --git a/goorm/build.gradle b/goorm/build.gradle index 8a270d2..4ef554f 100644 --- a/goorm/build.gradle +++ b/goorm/build.gradle @@ -35,6 +35,7 @@ dependencies { // testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index e799637..3a2d6b8 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -112,6 +112,7 @@ public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { // 해시태그 조회 List hashtagHistories = hashtagHistoryRepository.findByHistoryId(historyId); + //=> fetch join or jpql로 바로 가져오기 // 기록의 해시태그 조회 List hashtags = hashtagHistories.stream() .map(HashtagHistory::getHashtag) @@ -174,6 +175,8 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi throw new HistoryExeption(ErrorStatus.TOO_MANY_IMAGES); } + // 이미 그 날짜에 history 검증. + // member 1번이 로그인 한 유저라고 가정 Member member = memberRepository.findById(1L) .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_MEMBER)); diff --git a/goorm/src/main/resources/application-aws.yml b/goorm/src/main/resources/application-aws.yml new file mode 100644 index 0000000..caa7fac --- /dev/null +++ b/goorm/src/main/resources/application-aws.yml @@ -0,0 +1,9 @@ +cloud: + aws: + s3: + bucket: <버킷 이름> + stack.auto: false + region.static: ap-northeast-2 + credentials: + accessKey: <발급 받은 accessKey> + secretKey: <발급 받은 secretKey> \ No newline at end of file diff --git a/goorm/src/main/resources/application.yml b/goorm/src/main/resources/application.yml index 252c411..abe7049 100644 --- a/goorm/src/main/resources/application.yml +++ b/goorm/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + profiles: + include: jwt, aws #jwt.yml 불러오기 datasource: username: ${DB_USERNAME} password: ${DB_PASSWORD} From 8518c5d00eb8abceb8b0ed80bc99abda7ff0b668 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:43:12 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - S3 생성 #41 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7f02808..ead13f7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,5 @@ out/ ### VS Code ### .vscode/ -application-aws.yml +src/main/resources/application-aws.yml From b4f2f225feb4ebe7eaa778c995e87d804a9ebde8 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:47:33 +0900 Subject: [PATCH 15/21] Remove application-aws.yml from git tracking --- goorm/src/main/resources/application-aws.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 goorm/src/main/resources/application-aws.yml diff --git a/goorm/src/main/resources/application-aws.yml b/goorm/src/main/resources/application-aws.yml deleted file mode 100644 index caa7fac..0000000 --- a/goorm/src/main/resources/application-aws.yml +++ /dev/null @@ -1,9 +0,0 @@ -cloud: - aws: - s3: - bucket: <버킷 이름> - stack.auto: false - region.static: ap-northeast-2 - credentials: - accessKey: <발급 받은 accessKey> - secretKey: <발급 받은 secretKey> \ No newline at end of file From b53ff042f3380045528eeca4b12e5ac05b24b546 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Sun, 22 Jun 2025 17:49:01 +0900 Subject: [PATCH 16/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - S3 추가 #41 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ead13f7..b09f9b1 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,5 @@ out/ ### VS Code ### .vscode/ -src/main/resources/application-aws.yml +goorm/src/main/resources/application-aws.yml From 6c4f644e1ab68f01c30899cf925e8397d2765df2 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 25 Jun 2025 02:15:53 +0900 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좋아요 기능 - 좋아요 누른 유저 조회 #41 --- .../history/api/HistoryRestController.java | 35 +++++++++++++++++ .../history/application/HistoryService.java | 2 + .../application/HistoryServiceImpl.java | 38 +++++++++++++++++++ .../history/converter/HistoryConverter.java | 29 ++++++++++++++ .../domain/history/domain/entity/History.java | 8 ++++ .../repository/MemberLikeRepository.java | 2 + .../domain/history/dto/HistoryRequestDTO.java | 12 ++++++ .../history/dto/HistoryResponseDTO.java | 35 +++++++++++++++++ .../member/domain/dto/LikedMemberDTO.java | 17 +++++++++ .../domain/member/domain/entity/Member.java | 4 ++ .../member/domain/entity/MemberFollow.java | 18 +++++++++ .../domain/repository/MemberRepository.java | 23 +++++++++++ .../study/goorm/global/config/S3Config.java | 28 ++++++++++++++ .../global/error/code/status/ErrorStatus.java | 3 +- .../error/code/status/SuccessStatus.java | 4 +- .../study/goorm/storage/AwsFileService.java | 4 ++ 16 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java create mode 100644 goorm/src/main/java/study/goorm/domain/member/domain/entity/MemberFollow.java create mode 100644 goorm/src/main/java/study/goorm/global/config/S3Config.java create mode 100644 goorm/src/main/java/study/goorm/storage/AwsFileService.java diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index 3a3286a..fe64152 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -118,4 +118,39 @@ public BaseResponse patchHistory( return BaseResponse.onSuccess(SuccessStatus.HISTORY_UPDATED, null); } + + // 좋아요 기능 + @PostMapping("/like") + @Operation(summary = "특정 기록에 좋아요를 누를 수 있는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "좋아요 상태가 성공적으로 변경되었습니다."), + }) + public BaseResponse like( + Member member, + @RequestBody @Valid HistoryRequestDTO.HistoryLike historyLike + ) { + //isLiked의 상태에 따라서 좋아요 -> 취소 , 좋아요가 없는 상태 -> 좋아요 로 바꿔주게 됩니다. + HistoryResponseDTO.HistoryLikeResult result = historyService.changeLikeStatus(member.getId(), historyLike.getHistoryId(), historyLike.isLiked()); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_LIKE_STATUS_CHANGED, result); + } + + // 좋아요 누른 유저 조회 + @GetMapping("/{historyId}/likes") + @Operation(summary = "특정 기록에 좋아요를 누른 유저의 정보를 확인합니다.") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "기록의 좋아요를 누른 유저 정보를 성공적으로 조회했습니다."), + }) + @Parameters({ + @Parameter(name = "historyId", description = "기록의 id, path variable 입니다.") + }) + public BaseResponse getLikedUsers( + Member member, + @PathVariable Long historyId + ) { + + HistoryResponseDTO.HistoryLikedUserResultList result = historyService.getLikedUsers(member.getId(), historyId); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_LIKE_USER, result); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index c67f30e..3c978ff 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -14,4 +14,6 @@ public interface HistoryService { HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, List image); HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, List imageFile, Long historyId); void deleteHistory(Long historyId); + HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long historyId, boolean isLiked); + HistoryResponseDTO.HistoryLikedUserResultList getLikedUsers(Long memberId, Long historyId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 3a2d6b8..195a77c 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -13,6 +13,7 @@ import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.history.exception.HistoryExeption; +import study.goorm.domain.member.domain.dto.LikedMemberDTO; import study.goorm.domain.member.domain.entity.Member; import study.goorm.domain.member.domain.exception.MemberException; import study.goorm.domain.member.domain.repository.MemberRepository; @@ -340,4 +341,41 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi return HistoryConverter.toHistoryUpdateResult(history); } + + // 좋아요 기능 + @Override + @Transactional + public HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long historyId, boolean isLiked) { + History history = historyRepository.findById(historyId) + .orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + + // 이미 게시물이 좋아요한 상태일 경우 + if (isLiked) { + history.decreaseLikes(); + // 해당 게시물 좋아요 취소를 통해 멤버아이디와 기록아이디 삭제 + memberLikeRepository.deleteByMemberIdAndHistoryId(memberId, historyId); + } + else { // 게시물이 좋아요한 상태가 아닐 경우 + history.increaseLikes(); + // 해당 게시물 좋아요를 통해 멤버아이디와 기록아이디 등록 + MemberLike memberLike = MemberLike.builder() + .history(history) + .member(memberRepository.findMemberById(memberId)) + .build(); + memberLikeRepository.save(memberLike); + } + + return HistoryConverter.toHistoryLikeResult(history, isLiked); + } + + @Override + @Transactional(readOnly = true) + public HistoryResponseDTO.HistoryLikedUserResultList getLikedUsers( + Long loginMemberId, Long historyId + ) { + List likedMembers = + memberRepository.findLikedMembersWithFollowInfo(historyId, loginMemberId); + + return HistoryConverter.toLikedUserResult(likedMembers); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index f740d7e..9366ffa 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -2,11 +2,14 @@ import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; +import study.goorm.domain.member.domain.dto.LikedMemberDTO; import study.goorm.domain.member.domain.entity.Member; import study.goorm.domain.cloth.domain.entity.Cloth; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -50,4 +53,30 @@ public static HistoryResponseDTO.HistoryUpdateResult toHistoryUpdateResult(Histo .historyId(history.getId()) .build(); } + + public static HistoryResponseDTO.HistoryLikeResult toHistoryLikeResult(History history, boolean isLiked){ + return HistoryResponseDTO.HistoryLikeResult.builder() + .historyId(history.getId()) + .liked(!isLiked) + .likeCount(history.getLikes()) + .build(); + } + + public static HistoryResponseDTO.HistoryLikedUserResultList toLikedUserResult(List likedMembers){ + List likedUserResults = new ArrayList<>(); + for (int i = 0; i < likedMembers.size(); i++) { + LikedMemberDTO member = likedMembers.get(i); + likedUserResults.add(HistoryResponseDTO.HistoryLikedUserResult.builder() + .clokeyId(member.getClokeyId()) + .imageUrl(member.getImageUrl()) + .followStatus(member.getIsFollowed()) + .memberId(member.getMemberId()) + .nickname(member.getNickname()) + .isMe(member.getIsMyself()) + .build()); + } + return HistoryResponseDTO.HistoryLikedUserResultList.builder() + .likedUsers(likedUserResults) + .build(); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java index fe25f8d..ab01e42 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/History.java @@ -47,5 +47,13 @@ public class History extends BaseEntity { public void update(String content) { this.content = content; } + + public void decreaseLikes() { + this.likes = likes-1; + } + + public void increaseLikes() { + this.likes = likes+1; + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java index 3956d1d..55bf31c 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/MemberLikeRepository.java @@ -11,4 +11,6 @@ public interface MemberLikeRepository extends JpaRepository{ @Modifying @Query("DELETE FROM MemberLike l WHERE l.history = :history") void deleteByHistory(@Param("history") History history); + + void deleteByMemberIdAndHistoryId(Long memberId, Long historyId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java index a7a5c25..a89c83b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java @@ -47,4 +47,16 @@ public static class HistoryUpdateRequest { // private Visibility visibility; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryLike { + + private Long historyId; + + private boolean liked; + + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 466c6e4..c87fcb3 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -1,5 +1,6 @@ package study.goorm.domain.history.dto; +import jakarta.persistence.Column; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -74,4 +75,38 @@ public static class HistoryCreateResult { public static class HistoryUpdateResult { private Long historyId; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryLikeResult { + + private Long historyId; + + private boolean liked; + + private int likeCount; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryLikedUserResultList { + List likedUsers; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryLikedUserResult { + private Long memberId; + private String clokeyId; + private String nickname; + private boolean followStatus; + private String imageUrl; + private boolean isMe; + } } diff --git a/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java b/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java new file mode 100644 index 0000000..4e7f4af --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java @@ -0,0 +1,17 @@ +package study.goorm.domain.member.domain.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class LikedMemberDTO { + private Long memberId; + private String clokeyId; + private String imageUrl; + private String nickname; + Boolean isFollowed; + Boolean isMyself; +} diff --git a/goorm/src/main/java/study/goorm/domain/member/domain/entity/Member.java b/goorm/src/main/java/study/goorm/domain/member/domain/entity/Member.java index 4acc346..ef4692b 100644 --- a/goorm/src/main/java/study/goorm/domain/member/domain/entity/Member.java +++ b/goorm/src/main/java/study/goorm/domain/member/domain/entity/Member.java @@ -8,6 +8,10 @@ import study.goorm.domain.model.enums.MemberStatus; import study.goorm.domain.model.enums.SocialType; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + @Entity @Getter @Setter diff --git a/goorm/src/main/java/study/goorm/domain/member/domain/entity/MemberFollow.java b/goorm/src/main/java/study/goorm/domain/member/domain/entity/MemberFollow.java new file mode 100644 index 0000000..d42dc19 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/member/domain/entity/MemberFollow.java @@ -0,0 +1,18 @@ +package study.goorm.domain.member.domain.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name = "member_follow") +public class MemberFollow { + @Id + @GeneratedValue + private Long id; + + @ManyToOne @JoinColumn(name = "follower_id") + private Member follower; + + @ManyToOne @JoinColumn(name = "followed_id") + private Member followed; + +} diff --git a/goorm/src/main/java/study/goorm/domain/member/domain/repository/MemberRepository.java b/goorm/src/main/java/study/goorm/domain/member/domain/repository/MemberRepository.java index 31faac7..9e38e5b 100644 --- a/goorm/src/main/java/study/goorm/domain/member/domain/repository/MemberRepository.java +++ b/goorm/src/main/java/study/goorm/domain/member/domain/repository/MemberRepository.java @@ -1,10 +1,33 @@ package study.goorm.domain.member.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import study.goorm.domain.member.domain.dto.LikedMemberDTO; import study.goorm.domain.member.domain.entity.Member; +import java.util.List; import java.util.Optional; public interface MemberRepository extends JpaRepository{ Optional findByClokeyId(String clokeyId); + + Member findMemberById(Long memberId); + + @Query( + "SELECT new study.goorm.domain.member.domain.dto.LikedMemberDTO(" + + "m.id, m.clokeyId, m.profileImageUrl, m.nickname, " + + "CASE WHEN EXISTS(" + + "SELECT 1 FROM MemberFollow f " + + "WHERE f.follower.id = :loginMemberId AND f.followed.id = m.id" + + ") THEN true ELSE false END, " + + "CASE WHEN m.id = :loginMemberId THEN true ELSE false END" + + ") " + + "FROM MemberLike ml JOIN ml.member m " + + "WHERE ml.history.id = :historyId" + ) + List findLikedMembersWithFollowInfo( + @Param("historyId") Long historyId, + @Param("loginMemberId") Long loginMemberId + ); } diff --git a/goorm/src/main/java/study/goorm/global/config/S3Config.java b/goorm/src/main/java/study/goorm/global/config/S3Config.java new file mode 100644 index 0000000..306eea6 --- /dev/null +++ b/goorm/src/main/java/study/goorm/global/config/S3Config.java @@ -0,0 +1,28 @@ +package study.goorm.global.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class S3Config { + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials= new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index 000b36f..d11bf59 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -34,7 +34,8 @@ public enum ErrorStatus implements BaseErrorCode { INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, "HISTORY_4001", "잘못된 날짜 형식입니다."), NO_SUCH_HISTORY(HttpStatus.BAD_REQUEST, "HISTORY_4002","존재하지 않는 기록 ID 입니다."), TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"), - NO_PERMISSION_FOR_RECORD(HttpStatus.BAD_REQUEST, "HISTORY_4006","기록에 접근 권한이 없습니다.") + NO_PERMISSION_FOR_RECORD(HttpStatus.BAD_REQUEST, "HISTORY_4006","기록에 접근 권한이 없습니다."), + INVALID_LIKED(HttpStatus.BAD_REQUEST,"HISTORY_4004","잘못된 isLiked 값을 입력했습니다."), ; private final HttpStatus httpStatus; private final String code; diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index 88694c7..d2470d7 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -23,7 +23,9 @@ public enum SuccessStatus implements BaseCode { HISTORY_GET_MONTH(HttpStatus.OK, "HISTORY_200","월별 기록이 성공적으로 조회되었습니다."), HISTORY_CREATED(HttpStatus.CREATED,"HISTORY_201","옷 기록이 성공적으로 생성되었습니다."), HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."), - HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_200","성공적으로 수정되었습니다"); + HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_200","성공적으로 수정되었습니다"), + HISTORY_LIKE_STATUS_CHANGED(HttpStatus.OK,"HISTORY_200","좋아요 상태가 성공적으로 변경되었습니다."), + HISTORY_LIKE_USER(HttpStatus.OK,"HISTORY_200","기록의 좋아요를 누른 유저 정보를 성공적으로 조회했습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/goorm/src/main/java/study/goorm/storage/AwsFileService.java b/goorm/src/main/java/study/goorm/storage/AwsFileService.java new file mode 100644 index 0000000..33d2a77 --- /dev/null +++ b/goorm/src/main/java/study/goorm/storage/AwsFileService.java @@ -0,0 +1,4 @@ +package study.goorm.storage; + +public class AwsFileService { +} From dceb7cf4a3c506064ba684b7b7d5f7384f534c59 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Wed, 25 Jun 2025 20:49:12 +0900 Subject: [PATCH 18/21] =?UTF-8?q?feat:=20history=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 댓글 작성 - 댓글 조회 - 댓글 삭제 - 댓글 수정 #41 --- .../history/api/HistoryRestController.java | 77 +++++++++++++++++++ .../history/application/HistoryService.java | 4 + .../application/HistoryServiceImpl.java | 68 ++++++++++++++-- .../history/converter/HistoryConverter.java | 67 ++++++++++++++-- .../domain/history/domain/entity/Comment.java | 9 +++ .../domain/repository/CommentRepository.java | 15 +++- .../domain/repository/HistoryRepository.java | 25 ++++++ .../history/dto/HistoryCommentParamDTO.java | 21 +++++ .../domain/history/dto/HistoryRequestDTO.java | 23 ++++++ .../history/dto/HistoryResponseDTO.java | 64 +++++++++++++++ .../global/error/code/status/ErrorStatus.java | 3 +- .../error/code/status/SuccessStatus.java | 7 +- 12 files changed, 369 insertions(+), 14 deletions(-) create mode 100644 goorm/src/main/java/study/goorm/domain/history/dto/HistoryCommentParamDTO.java diff --git a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java index fe64152..62b96b6 100644 --- a/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java +++ b/goorm/src/main/java/study/goorm/domain/history/api/HistoryRestController.java @@ -153,4 +153,81 @@ public BaseResponse getLikedUsers return BaseResponse.onSuccess(SuccessStatus.HISTORY_LIKE_USER, result); } + + // 댓글 추가 + @PostMapping("/{historyId}/comments") + @Operation(summary = "댓글을 남길 수 있는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_201", description = "성공적으로 댓글이 생성되었습니다."), + }) + @Parameters({ + @Parameter(name = "historyId", description = "댓글을 남기고자 하는 기록의 ID") + }) + public BaseResponse writeComments( + @PathVariable @Valid Long historyId, + @RequestBody @Valid HistoryRequestDTO.HistoryCommentWrite request, + Member member + ) { + return BaseResponse.onSuccess(SuccessStatus.HISTORY_COMMENT_CREATED, historyService.writeComment(historyId, request.getCommentId(), member.getId(), request.getContent())); + + } + + // 댓글 조회 + @GetMapping("/{historyId}/comments") + @Operation(summary = "특정 기록의 댓글을 읽어올 수 있는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_200", description = "OK, 성공적으로 조회되었습니다.") + }) + @Parameters({ + @Parameter(name = "historyId", description = "기록의 id, path variable 입니다."), + @Parameter(name = "page", description = "페이징 관련 query parameter") + + }) + public BaseResponse getComments( + @PathVariable @Valid Long historyId, + @RequestParam(value = "page") @Valid @CheckPage int page + ) { + + HistoryResponseDTO.HistoryCommentResult result = historyService.getComments(historyId, page - 1); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_SUCCESS, result); + } + + @DeleteMapping(value = "/comments/{commentId}") + @Operation(summary = "댓글을 삭제하는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_204", description = "댓글이 성공적으로 삭제되었습니다."), + }) + @Parameters({ + @Parameter(name = "commentId", description = "삭제하고자 하는 댓글의 ID") + }) + public BaseResponse deleteComment( + Member member, + @PathVariable Long commentId + ) { + + historyService.deleteComment(commentId, member.getId()); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_COMMENT_DELETED, null); + } + + @PatchMapping(value = "/comments/{commentId}") + @Operation(summary = "댓글을 수정하는 API") + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_204", description = "댓글이 성공적으로 수정되었습니다."), + }) + @Parameters({ + @Parameter(name = "commentId", description = "수정하고자 하는 댓글의 ID") + }) + public BaseResponse updateComment( + @RequestBody @Valid HistoryRequestDTO.HistoryUpdateComment updateCommentRequest, + @PathVariable Long commentId, + Member member + ) { + + historyService.updateComment(updateCommentRequest, commentId, member.getId()); + + return BaseResponse.onSuccess(SuccessStatus.HISTORY_COMMENT_UPDATED, null); + } + } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index 3c978ff..1653026 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -16,4 +16,8 @@ public interface HistoryService { void deleteHistory(Long historyId); HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long historyId, boolean isLiked); HistoryResponseDTO.HistoryLikedUserResultList getLikedUsers(Long memberId, Long historyId); + HistoryResponseDTO.HistoryCommentWriteResult writeComment(Long historyId, Long commentId, Long memberId, String content); + HistoryResponseDTO.HistoryCommentResult getComments(Long historyId, int page); + void deleteComment(Long commentId, Long memberId); + void updateComment(HistoryRequestDTO.HistoryUpdateComment updateCommentRequest, Long commentId, Long memberId); } diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 195a77c..d45a01e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -1,6 +1,8 @@ package study.goorm.domain.history.application; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -10,6 +12,7 @@ import study.goorm.domain.history.converter.HistoryConverter; import study.goorm.domain.history.domain.entity.*; import study.goorm.domain.history.domain.repository.*; +import study.goorm.domain.history.dto.HistoryCommentParamDTO; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.history.exception.HistoryExeption; @@ -51,10 +54,10 @@ public HistoryResponseDTO.HistoryGetMonthly getHistoryGetMonthly(String clokeyId // clokeyId가 null인 경우 if (clokeyId == null) { member = memberRepository.findByClokeyId("1") - .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + .orElseThrow(() -> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); } else { // clokeyId가 있는 경우 member = memberRepository.findByClokeyId(clokeyId) - .orElseThrow(()-> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); + .orElseThrow(() -> new MemberException(ErrorStatus.NO_SUCH_MEMBER)); } // Month 형식 검사 @@ -122,7 +125,7 @@ public HistoryResponseDTO.HistoryGetDaily getHistoryGetDaily(Long historyId) { .map(Hashtag::getName) .toList(); - List cloths = clothRepository.findByMemberId(member.getId()); + List cloths = clothRepository.findByMemberId(member.getId()); List clothList = cloths.stream() .map(cloth -> HistoryResponseDTO.HistoryGetDailyCloth.builder() @@ -347,15 +350,14 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi @Transactional public HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long historyId, boolean isLiked) { History history = historyRepository.findById(historyId) - .orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); // 이미 게시물이 좋아요한 상태일 경우 if (isLiked) { history.decreaseLikes(); // 해당 게시물 좋아요 취소를 통해 멤버아이디와 기록아이디 삭제 memberLikeRepository.deleteByMemberIdAndHistoryId(memberId, historyId); - } - else { // 게시물이 좋아요한 상태가 아닐 경우 + } else { // 게시물이 좋아요한 상태가 아닐 경우 history.increaseLikes(); // 해당 게시물 좋아요를 통해 멤버아이디와 기록아이디 등록 MemberLike memberLike = MemberLike.builder() @@ -378,4 +380,58 @@ public HistoryResponseDTO.HistoryLikedUserResultList getLikedUsers( return HistoryConverter.toLikedUserResult(likedMembers); } + + @Override + @Transactional + public HistoryResponseDTO.HistoryCommentWriteResult writeComment(Long historyId, Long commentId, Long memberId, String content) { + History history = historyRepository.findById(historyId) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); + + Member member = memberRepository.findMemberById(memberId); + + Comment parentComment = null; + + if (commentId != null) { + parentComment = commentRepository.findById(commentId) + .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_COMMENT)); + ; + } + + Comment comment = Comment.builder() + .content(content) + .comment(parentComment) + .history(history) + .member(member) + .build(); + + Comment savedComment = commentRepository.save(comment); + + return HistoryConverter.toCommentWriteResult(savedComment); + } + + @Override + @Transactional(readOnly = true) + public HistoryResponseDTO.HistoryCommentResult getComments(Long historyId, int page) { + // 한 페이지당 10개씩 + Pageable pageable = PageRequest.of(page, 10); + List commentsDTO = + historyRepository.findFlatCommentsByHistoryId(historyId, pageable); + + int totalRootCount = commentRepository.countActiveRootComments(historyId); + return HistoryConverter.toHistoryCommentResult(commentsDTO, page, 20, totalRootCount); + } + + @Override + @Transactional + public void deleteComment(Long commentId, Long memberId) { + commentRepository.deleteChildrenComment(commentId); + commentRepository.deleteById(commentId); + } + + @Override + @Transactional + public void updateComment(HistoryRequestDTO.HistoryUpdateComment updateCommentRequest, Long commentId, Long memberId) { + Comment comment = commentRepository.findById(commentId).orElseThrow(()-> new HistoryExeption(ErrorStatus.NO_SUCH_COMMENT)); + comment.updateContent(updateCommentRequest.getContent()); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index 9366ffa..2b6a01d 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -1,7 +1,9 @@ package study.goorm.domain.history.converter; +import study.goorm.domain.history.domain.entity.Comment; import study.goorm.domain.history.domain.entity.History; import study.goorm.domain.history.domain.entity.HistoryImage; +import study.goorm.domain.history.dto.HistoryCommentParamDTO; import study.goorm.domain.history.dto.HistoryRequestDTO; import study.goorm.domain.history.dto.HistoryResponseDTO; import study.goorm.domain.member.domain.dto.LikedMemberDTO; @@ -11,6 +13,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; public class HistoryConverter { @@ -24,7 +27,7 @@ public static HistoryResponseDTO.HistoryGetMonthly toHistoryGetMonthly(Member me .build(); } - public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, List images, Member member, List hashtags, List cloths){ + public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History history, List images, Member member, List hashtags, List cloths) { return HistoryResponseDTO.HistoryGetDaily.builder() .memberId(member.getId()) .historyId(history.getId()) @@ -42,19 +45,19 @@ public static HistoryResponseDTO.HistoryGetDaily toHistoryGetDaily(History histo .build(); } - public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history){ + public static HistoryResponseDTO.HistoryCreateResult toHistoryCreateResult(History history) { return HistoryResponseDTO.HistoryCreateResult.builder() .historyId(history.getId()) .build(); } - public static HistoryResponseDTO.HistoryUpdateResult toHistoryUpdateResult(History history){ + public static HistoryResponseDTO.HistoryUpdateResult toHistoryUpdateResult(History history) { return HistoryResponseDTO.HistoryUpdateResult.builder() .historyId(history.getId()) .build(); } - public static HistoryResponseDTO.HistoryLikeResult toHistoryLikeResult(History history, boolean isLiked){ + public static HistoryResponseDTO.HistoryLikeResult toHistoryLikeResult(History history, boolean isLiked) { return HistoryResponseDTO.HistoryLikeResult.builder() .historyId(history.getId()) .liked(!isLiked) @@ -62,7 +65,7 @@ public static HistoryResponseDTO.HistoryLikeResult toHistoryLikeResult(History h .build(); } - public static HistoryResponseDTO.HistoryLikedUserResultList toLikedUserResult(List likedMembers){ + public static HistoryResponseDTO.HistoryLikedUserResultList toLikedUserResult(List likedMembers) { List likedUserResults = new ArrayList<>(); for (int i = 0; i < likedMembers.size(); i++) { LikedMemberDTO member = likedMembers.get(i); @@ -79,4 +82,58 @@ public static HistoryResponseDTO.HistoryLikedUserResultList toLikedUserResult(Li .likedUsers(likedUserResults) .build(); } + + public static HistoryResponseDTO.HistoryCommentWriteResult toCommentWriteResult(Comment comment) { + return HistoryResponseDTO.HistoryCommentWriteResult.builder() + .commentId(comment.getId()) + .build(); + } + + public static HistoryResponseDTO.HistoryCommentResult toHistoryCommentResult( + List flatComments, + int page, + int pageSize, + int totalRootCount + ) { + // 부모 댓글 id를 기준 + Map> repliesGrouped = flatComments.stream() + .filter(dto -> !dto.isRoot()) // 대댓글 + .collect(Collectors.groupingBy(HistoryCommentParamDTO::getParentId)); + + // 댓글과 대댓글 연결해주기 + List rootResults = flatComments.stream() + .filter(HistoryCommentParamDTO::isRoot) + .map(root -> HistoryResponseDTO.CommentResult.builder() + .commentId(root.getCommentId()) + .content(root.getContent()) + .clokeyId(root.getClokeyId()) + .nickName(root.getNickname()) + .userImageUrl(root.getProfileImageUrl()) + .replyResults( + repliesGrouped.getOrDefault(root.getCommentId(), List.of()).stream() + .map(reply -> HistoryResponseDTO.ReplyResult.builder() + .commentId(reply.getCommentId()) + .content(reply.getContent()) + .clokeyId(reply.getClokeyId()) + .nickName(reply.getNickname()) + .userImageUrl(reply.getProfileImageUrl()) + .build()) + .toList() + ) + .build()) + .toList(); + + int totalPage = (int) Math.ceil((double) totalRootCount / pageSize); + int totalElements = rootResults.stream() + .mapToInt(r -> 1 + (r.getReplyResults() != null ? r.getReplyResults().size() : 0)) + .sum(); + + return HistoryResponseDTO.HistoryCommentResult.builder() + .comments(rootResults) + .totalPage(totalPage) + .totalElements(totalElements) + .isFirst(page == 0) + .isLast(page + 1 == totalPage) + .build(); + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/entity/Comment.java b/goorm/src/main/java/study/goorm/domain/history/domain/entity/Comment.java index 2b8a337..17c3dac 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/entity/Comment.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/entity/Comment.java @@ -30,4 +30,13 @@ public class Comment extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private Comment comment; + + @Column(nullable = false) + private boolean banned = false; + + public void updateContent(String content) { + if (content != null && !content.isEmpty()) { + this.content = content; + } + } } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java index 5d48f12..34a6ff8 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/CommentRepository.java @@ -4,12 +4,25 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import study.goorm.domain.history.domain.entity.Comment; import study.goorm.domain.history.domain.entity.History; public interface CommentRepository extends JpaRepository{ - long countByHistoryId(Long historyId); + @Query("SELECT COUNT(c) FROM Comment c WHERE c.history.id = :historyId") + Long countByHistoryId(@Param("historyId") Long historyId); + @Modifying @Query("DELETE FROM Comment c WHERE c.history = :history") void deleteByHistory(@Param("history") History history); + + @Query("SELECT COUNT(c) FROM Comment c WHERE c.history.id = :historyId AND c.comment IS NULL AND c.banned = false") + int countActiveRootComments(@Param("historyId") Long historyId); + + // 대댓 삭제 + @Transactional + @Modifying + @Query("DELETE FROM Comment c WHERE c.comment.id = :commentId") + void deleteChildrenComment(@Param("commentId") Long commentId); + } diff --git a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java index 6b23ef6..a40b44e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java +++ b/goorm/src/main/java/study/goorm/domain/history/domain/repository/HistoryRepository.java @@ -1,10 +1,13 @@ package study.goorm.domain.history.domain.repository; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import study.goorm.domain.history.domain.entity.History; +import study.goorm.domain.history.dto.HistoryCommentParamDTO; import study.goorm.domain.member.domain.entity.Member; +import study.goorm.domain.history.domain.entity.Comment; import java.time.LocalDate; import java.util.List; @@ -16,4 +19,26 @@ public interface HistoryRepository extends JpaRepository{ List findHistoriesByMemberIdAndYearMonth(@Param("memberId") Long memberId, @Param("yearMonth") String yearMonth); History findById(long historyId); + + @Query( + "SELECT new study.goorm.domain.history.dto.HistoryCommentParamDTO(" + + " c.id, " + // commentId + " c.content, " + // content + " CASE WHEN c.comment.id IS NULL THEN true ELSE false END, " + // isRoot + " c.comment.id, " + // parentId + " c.member.clokeyId, " + // clokeyId + " c.member.nickname, " + // nickname + " c.member.profileImageUrl, " + // profileImageUrl + " c.createdAt" + // createdAt + ") " + + "FROM Comment c " + + "WHERE c.history.id = :historyId " + + "ORDER BY " + + " COALESCE(c.comment.id, c.id) ASC, " + + " c.createdAt ASC" + ) + List findFlatCommentsByHistoryId( + @Param("historyId") Long historyId, + Pageable pageable + ); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryCommentParamDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryCommentParamDTO.java new file mode 100644 index 0000000..753e9b6 --- /dev/null +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryCommentParamDTO.java @@ -0,0 +1,21 @@ +package study.goorm.domain.history.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class HistoryCommentParamDTO { + private Long commentId; + private String content; + private boolean isRoot; + private Long parentId; + private String clokeyId; + private String nickname; + private String profileImageUrl; + private LocalDateTime createdAt; +} diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java index a89c83b..5b71bb4 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryRequestDTO.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -59,4 +60,26 @@ public static class HistoryLike { private boolean liked; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryCommentWrite { + + Long commentId; + + @NotBlank + String content; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryUpdateComment { + @NotBlank + String content; + } + } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index c87fcb3..4a6df73 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -1,5 +1,8 @@ package study.goorm.domain.history.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import jakarta.persistence.Column; import lombok.AllArgsConstructor; import lombok.Builder; @@ -109,4 +112,65 @@ public static class HistoryLikedUserResult { private String imageUrl; private boolean isMe; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryCommentWriteResult { + Long commentId; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class HistoryCommentResult { + List comments; + int totalPage; + int totalElements; + + @JsonProperty("isFirst") // JSON 직렬화 시 "isFirst" 사용 + private boolean isFirst; + + @JsonIgnore // "first" 필드 직렬화 방지 + public boolean isFirst() { + return isFirst; + } + + @JsonProperty("isLast") // JSON 직렬화 시 "isLast" 사용 + private boolean isLast; + + @JsonIgnore // "last" 필드 직렬화 방지 + public boolean isLast() { + return isLast; + } + } + + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + @JsonPropertyOrder({"commentId", "clokeyId", "nickName", "userImageUrl", "content", "replyResults"}) + public static class CommentResult { + Long commentId; + String clokeyId; + String nickName; + String userImageUrl; + String content; + List replyResults; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class ReplyResult { + Long commentId; + String clokeyId; + String nickName; + String userImageUrl; + String content; + } } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index d11bf59..5b59b2d 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -36,7 +36,8 @@ public enum ErrorStatus implements BaseErrorCode { TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"), NO_PERMISSION_FOR_RECORD(HttpStatus.BAD_REQUEST, "HISTORY_4006","기록에 접근 권한이 없습니다."), INVALID_LIKED(HttpStatus.BAD_REQUEST,"HISTORY_4004","잘못된 isLiked 값을 입력했습니다."), - ; + NO_SUCH_COMMENT(HttpStatus.NOT_FOUND,"HISTORY_4005","존재하지 않는 댓글 ID입니다."); + private final HttpStatus httpStatus; private final String code; private final String message; diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java index d2470d7..36437d2 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/SuccessStatus.java @@ -25,7 +25,12 @@ public enum SuccessStatus implements BaseCode { HISTORY_DELETED(HttpStatus.NO_CONTENT, "HISTORY_202","기록이 성공적으로 삭제되었습니다."), HISTORY_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_200","성공적으로 수정되었습니다"), HISTORY_LIKE_STATUS_CHANGED(HttpStatus.OK,"HISTORY_200","좋아요 상태가 성공적으로 변경되었습니다."), - HISTORY_LIKE_USER(HttpStatus.OK,"HISTORY_200","기록의 좋아요를 누른 유저 정보를 성공적으로 조회했습니다."); + HISTORY_LIKE_USER(HttpStatus.OK,"HISTORY_200","기록의 좋아요를 누른 유저 정보를 성공적으로 조회했습니다."), + HISTORY_COMMENT_CREATED(HttpStatus.CREATED,"HISTORY_201","성공적으로 댓글이 생성되었습니다."), + HISTORY_SUCCESS(HttpStatus.OK, "HISTORY_200", "성공적으로 조회되었습니다."), + HISTORY_COMMENT_DELETED(HttpStatus.NO_CONTENT,"HISTORY_204","댓글이 성공적으로 삭제되었습니다"), + HISTORY_COMMENT_UPDATED(HttpStatus.NO_CONTENT,"HISTORY_204","댓글이 성공적으로 수정되었습니다"), + NOT_MY_COMMENT(HttpStatus.BAD_REQUEST,"HISTORY_4010","나의 댓글이 아닙니다"),; private final HttpStatus httpStatus; private final String code; From d712df7a752c2c5d1915aa030b8995eedafbc655 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:02:46 +0900 Subject: [PATCH 19/21] =?UTF-8?q?fix:=20History=20api=20=EB=A7=88=EB=AC=B4?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 남은 History api 마무리 하기 #41 --- .../history/application/HistoryService.java | 1 + .../application/HistoryServiceImpl.java | 58 +++++++++++-------- .../history/converter/HistoryConverter.java | 2 +- .../history/dto/HistoryResponseDTO.java | 13 ----- .../global/error/code/status/ErrorStatus.java | 5 +- .../study/goorm/storage/AwsFileService.java | 4 -- .../study/goorm/storage/S3UploadService.java | 31 ++++++++++ goorm/src/main/resources/application.yml | 13 +++++ 8 files changed, 85 insertions(+), 42 deletions(-) delete mode 100644 goorm/src/main/java/study/goorm/storage/AwsFileService.java create mode 100644 goorm/src/main/java/study/goorm/storage/S3UploadService.java diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java index 1653026..360867a 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryService.java @@ -14,6 +14,7 @@ public interface HistoryService { HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateResult, List image); HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, List imageFile, Long historyId); void deleteHistory(Long historyId); + HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long historyId, boolean isLiked); HistoryResponseDTO.HistoryLikedUserResultList getLikedUsers(Long memberId, Long historyId); HistoryResponseDTO.HistoryCommentWriteResult writeComment(Long historyId, Long commentId, Long memberId, String content); diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index d45a01e..755b972 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -21,6 +21,7 @@ import study.goorm.domain.member.domain.exception.MemberException; import study.goorm.domain.member.domain.repository.MemberRepository; import study.goorm.global.error.code.status.ErrorStatus; +import study.goorm.storage.S3UploadService; import java.io.File; import java.io.IOException; @@ -44,6 +45,7 @@ public class HistoryServiceImpl implements HistoryService { private final HashtagRepository hashtagRepository; private final CommentRepository commentRepository; private final MemberLikeRepository memberLikeRepository; + private final S3UploadService s3UploadService; @Override @@ -247,6 +249,25 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi .toList(); hashtagHistoryRepository.saveAll(hashtagHistories); + List historyImages = new ArrayList<>(); + + for (MultipartFile file : image) { + if (file.isEmpty()) continue; + + try { + String imageUrl = s3UploadService.saveFile(file); // S3에 업로드 + HistoryImage historyImage = HistoryImage.builder() + .history(history) + .imageUrl(imageUrl) + .build(); + historyImages.add(historyImage); + } catch (IOException e) { + throw new HistoryExeption(ErrorStatus.S3_IMAGE_UPLOAD_FAIL); // 필요시 에러코드 추가 + } + } + + historyImageRepository.saveAll(historyImages); + return HistoryConverter.toHistoryCreateResult(history); } @@ -314,30 +335,21 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi // DB에서 HistoryImage 삭제 historyImageRepository.deleteAllByHistory(history); - String uploadDir = "history/"; // 상대 경로 또는 절대 경로 + // S3 업로드 로직 List newHistoryImages = new ArrayList<>(); for (MultipartFile file : images) { if (file.isEmpty()) continue; - - String originalFilename = file.getOriginalFilename(); - String extension = originalFilename.substring(originalFilename.lastIndexOf(".")); - String uniqueName = UUID.randomUUID().toString() + extension; - - File dest = new File(uploadDir + uniqueName); try { - file.transferTo(dest); // 실제 파일 저장 + String imageUrl = s3UploadService.saveFile(file); // Upload to S3 + HistoryImage historyImage = HistoryImage.builder() + .history(history) + .imageUrl(imageUrl) + .build(); + newHistoryImages.add(historyImage); } catch (IOException e) { - throw new RuntimeException("이미지 저장 실패", e); + throw new HistoryExeption(ErrorStatus.S3_IMAGE_UPLOAD_FAIL); } - - String imageUrl = "/uploads/history/" + uniqueName; // 클라이언트 접근 경로 - HistoryImage historyImage = HistoryImage.builder() - .history(history) - .imageUrl(imageUrl) - .build(); - - newHistoryImages.add(historyImage); } historyImageRepository.saveAll(newHistoryImages); @@ -352,12 +364,8 @@ public HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long History history = historyRepository.findById(historyId) .orElseThrow(() -> new HistoryExeption(ErrorStatus.NO_SUCH_HISTORY)); - // 이미 게시물이 좋아요한 상태일 경우 + // 게시물에 좋아요한 상태일 경우 if (isLiked) { - history.decreaseLikes(); - // 해당 게시물 좋아요 취소를 통해 멤버아이디와 기록아이디 삭제 - memberLikeRepository.deleteByMemberIdAndHistoryId(memberId, historyId); - } else { // 게시물이 좋아요한 상태가 아닐 경우 history.increaseLikes(); // 해당 게시물 좋아요를 통해 멤버아이디와 기록아이디 등록 MemberLike memberLike = MemberLike.builder() @@ -365,6 +373,10 @@ public HistoryResponseDTO.HistoryLikeResult changeLikeStatus(Long memberId, Long .member(memberRepository.findMemberById(memberId)) .build(); memberLikeRepository.save(memberLike); + } else { // 게시물에 좋아요한 상태가 아닐 경우 + history.decreaseLikes(); + // 해당 게시물 좋아요 취소를 통해 멤버아이디와 기록아이디 삭제 + memberLikeRepository.deleteByMemberIdAndHistoryId(memberId, historyId); } return HistoryConverter.toHistoryLikeResult(history, isLiked); @@ -418,7 +430,7 @@ public HistoryResponseDTO.HistoryCommentResult getComments(Long historyId, int p historyRepository.findFlatCommentsByHistoryId(historyId, pageable); int totalRootCount = commentRepository.countActiveRootComments(historyId); - return HistoryConverter.toHistoryCommentResult(commentsDTO, page, 20, totalRootCount); + return HistoryConverter.toHistoryCommentResult(commentsDTO, page, 10, totalRootCount); } @Override diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index 2b6a01d..ff38d6b 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -60,7 +60,7 @@ public static HistoryResponseDTO.HistoryUpdateResult toHistoryUpdateResult(Histo public static HistoryResponseDTO.HistoryLikeResult toHistoryLikeResult(History history, boolean isLiked) { return HistoryResponseDTO.HistoryLikeResult.builder() .historyId(history.getId()) - .liked(!isLiked) + .liked(isLiked) .likeCount(history.getLikes()) .build(); } diff --git a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java index 4a6df73..997803e 100644 --- a/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java +++ b/goorm/src/main/java/study/goorm/domain/history/dto/HistoryResponseDTO.java @@ -129,22 +129,9 @@ public static class HistoryCommentResult { List comments; int totalPage; int totalElements; - - @JsonProperty("isFirst") // JSON 직렬화 시 "isFirst" 사용 private boolean isFirst; - - @JsonIgnore // "first" 필드 직렬화 방지 - public boolean isFirst() { - return isFirst; - } - - @JsonProperty("isLast") // JSON 직렬화 시 "isLast" 사용 private boolean isLast; - @JsonIgnore // "last" 필드 직렬화 방지 - public boolean isLast() { - return isLast; - } } diff --git a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java index 5b59b2d..e1739e0 100644 --- a/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java +++ b/goorm/src/main/java/study/goorm/global/error/code/status/ErrorStatus.java @@ -36,7 +36,10 @@ public enum ErrorStatus implements BaseErrorCode { TOO_MANY_IMAGES(HttpStatus.BAD_REQUEST, "HISTORY_4002","이미지 업로드 개수를 초과했습니다"), NO_PERMISSION_FOR_RECORD(HttpStatus.BAD_REQUEST, "HISTORY_4006","기록에 접근 권한이 없습니다."), INVALID_LIKED(HttpStatus.BAD_REQUEST,"HISTORY_4004","잘못된 isLiked 값을 입력했습니다."), - NO_SUCH_COMMENT(HttpStatus.NOT_FOUND,"HISTORY_4005","존재하지 않는 댓글 ID입니다."); + NO_SUCH_COMMENT(HttpStatus.NOT_FOUND,"HISTORY_4005","존재하지 않는 댓글 ID입니다."), + + // S3 + S3_IMAGE_UPLOAD_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "S3_001", "S3 이미지 업로드 실패"); private final HttpStatus httpStatus; private final String code; diff --git a/goorm/src/main/java/study/goorm/storage/AwsFileService.java b/goorm/src/main/java/study/goorm/storage/AwsFileService.java deleted file mode 100644 index 33d2a77..0000000 --- a/goorm/src/main/java/study/goorm/storage/AwsFileService.java +++ /dev/null @@ -1,4 +0,0 @@ -package study.goorm.storage; - -public class AwsFileService { -} diff --git a/goorm/src/main/java/study/goorm/storage/S3UploadService.java b/goorm/src/main/java/study/goorm/storage/S3UploadService.java new file mode 100644 index 0000000..a20dec7 --- /dev/null +++ b/goorm/src/main/java/study/goorm/storage/S3UploadService.java @@ -0,0 +1,31 @@ +package study.goorm.storage; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; + +@Service +@RequiredArgsConstructor +public class S3UploadService { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String saveFile(MultipartFile multipartFile) throws IOException { + String originalFilename = multipartFile.getOriginalFilename(); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(multipartFile.getSize()); + metadata.setContentType(multipartFile.getContentType()); + + amazonS3.putObject(bucket, originalFilename, multipartFile.getInputStream(), metadata); + return amazonS3.getUrl(bucket, originalFilename).toString(); + } +} diff --git a/goorm/src/main/resources/application.yml b/goorm/src/main/resources/application.yml index abe7049..30db233 100644 --- a/goorm/src/main/resources/application.yml +++ b/goorm/src/main/resources/application.yml @@ -28,3 +28,16 @@ logging: org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql.BasicBinder: TRACE org.springframework.jdbc.core: DEBUG + +cloud: + aws: + s3: + bucket: ${BUCKET} + credentials: + access-key: ${ACCESS_KEY} + secret-key: ${SECRET_KEY} + region: + static: ap-northeast-2 + auto: false + stack: + auto: false From e271d0c40acd976ac862529e3aa8ce17e138dd9c Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:02:53 +0900 Subject: [PATCH 20/21] =?UTF-8?q?fix:=20History=20api=20=EB=A7=88=EB=AC=B4?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 최종 마무리 #41 --- goorm/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goorm/src/main/resources/application.yml b/goorm/src/main/resources/application.yml index 30db233..c99ce3d 100644 --- a/goorm/src/main/resources/application.yml +++ b/goorm/src/main/resources/application.yml @@ -37,7 +37,7 @@ cloud: access-key: ${ACCESS_KEY} secret-key: ${SECRET_KEY} region: - static: ap-northeast-2 + static: ap-southeast-2 auto: false stack: auto: false From 6df0c398a91c083590ea8daddeb3d817498a45f1 Mon Sep 17 00:00:00 2001 From: geumjoon <152842614+Funital@users.noreply.github.com> Date: Fri, 27 Jun 2025 22:11:05 +0900 Subject: [PATCH 21/21] =?UTF-8?q?fix:=20History=20api=20=EC=B5=9C=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 #41: --- .../goorm/domain/history/application/HistoryServiceImpl.java | 2 +- .../study/goorm/domain/history/converter/HistoryConverter.java | 2 +- .../study/goorm/domain/member/domain/dto/LikedMemberDTO.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java index 755b972..0cf9ce1 100644 --- a/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java +++ b/goorm/src/main/java/study/goorm/domain/history/application/HistoryServiceImpl.java @@ -262,7 +262,7 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi .build(); historyImages.add(historyImage); } catch (IOException e) { - throw new HistoryExeption(ErrorStatus.S3_IMAGE_UPLOAD_FAIL); // 필요시 에러코드 추가 + throw new HistoryExeption(ErrorStatus.S3_IMAGE_UPLOAD_FAIL); } } diff --git a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java index ff38d6b..b6f562a 100644 --- a/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java +++ b/goorm/src/main/java/study/goorm/domain/history/converter/HistoryConverter.java @@ -75,7 +75,7 @@ public static HistoryResponseDTO.HistoryLikedUserResultList toLikedUserResult(Li .followStatus(member.getIsFollowed()) .memberId(member.getMemberId()) .nickname(member.getNickname()) - .isMe(member.getIsMyself()) + .isMe(member.getIsMe()) .build()); } return HistoryResponseDTO.HistoryLikedUserResultList.builder() diff --git a/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java b/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java index 4e7f4af..ff433b0 100644 --- a/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java +++ b/goorm/src/main/java/study/goorm/domain/member/domain/dto/LikedMemberDTO.java @@ -13,5 +13,5 @@ public class LikedMemberDTO { private String imageUrl; private String nickname; Boolean isFollowed; - Boolean isMyself; + Boolean isMe; }