Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.springframework.data.domain.Page;
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.cloth.domain.entity.Cloth;
import study.goorm.domain.member.domain.entity.Member;

Expand All @@ -24,4 +26,8 @@ public interface ClothRepository extends JpaRepository<Cloth, Long> {
Page<Cloth> findByMemberOrderByCreatedAtDesc(Member member, Pageable pageable);

List<Cloth> findByMemberId(Long memberId);

@Query("SELECT c FROM Cloth c WHERE c.id IN :ids AND c.member.id = :memberId")
List<Cloth> findAllByIdsAndMemberId(@Param("ids") List<Long> ids, @Param("memberId") Long memberId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public BaseResponse<HistoryResponseDTO.HistoryUpdateResult> updateHistory(
return BaseResponse.onSuccess(SuccessStatus.HISTORY_UPDATED, result);
}

// 기록 삭제
@DeleteMapping("{history-id}")
@Operation(summary = "유저의 날짜별 옷 기록을 삭제하는 API", description = "path variable로 history-id를 넘겨주세요.")
@ApiResponses({
Expand All @@ -104,4 +105,31 @@ public BaseResponse<Void> deleteHistory(

return BaseResponse.onSuccess(SuccessStatus.HISTORY_DELETED, null);
}

// 좋아요 추가 / 삭제
@PostMapping("/like")
@Operation(summary = "기록의 좋아요를 추가하고 삭제하는 API", description = "request body에 좋아요 상태와 history-id를 넘겨주세요.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_LIKE_201", description = "CREATED, 좋아요 상태가 성공적으로 변경되었습니다..")
})
public BaseResponse<HistoryResponseDTO.HistoryLikeResult> like(
@RequestBody @Valid HistoryRequestDTO.HistoryLikeRequest historyLikeRequest) {

HistoryResponseDTO.HistoryLikeResult result = historyService.likeHistory(historyLikeRequest.getHistoryId(), historyLikeRequest.isLiked());

return BaseResponse.onSuccess(SuccessStatus.HISTORY_LIKE_CREATED, result);
}

// 댓글 작성
@PostMapping("/{historyId}/comments")
@Operation(summary = "댓글을 작성하는 API", description = "path variable에 history-id를 넘겨주시고, request body에 commentId와 content를 넘겨주세요.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "HISTORY_COMMENT_201", description = "CREATED, 댓글이 성공적으로 생성되었습니다.")
})
public BaseResponse<HistoryResponseDTO.CommentWriteResult> writeComment(
@PathVariable @Valid Long historyId,
@RequestBody @Valid HistoryRequestDTO.CommentWriteRequest commentWriteRequest) {

return BaseResponse.onSuccess(SuccessStatus.HISTORY_COMMENT_CREATED, historyService.writeComment(historyId, commentWriteRequest.getCommentId(), commentWriteRequest.getContent()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

public interface HistoryService {

// 기록
HistoryResponseDTO.MonthlyHistoryPreview getMonthlyPreview(String clokeyId, String date);

HistoryResponseDTO.DailyHistoryPreview getDailyPreview(Long historyId);
Expand All @@ -18,4 +19,10 @@ public interface HistoryService {
HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest historyUpdateRequest, List<MultipartFile> imageFile, Long historyId);

void deleteHistory(Long historyId);

// 좋아요
HistoryResponseDTO.HistoryLikeResult likeHistory(Long historyId, boolean liked);

// 댓글 작성
HistoryResponseDTO.CommentWriteResult writeComment(Long historyId, Long parentCommentId, String content);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ public class HistoryServiceImpl implements HistoryService {
private final ClothRepository clothRepository;
private final HistoryClothRepository historyClothRepository;
private final HashtagRepository hashtagRepository;
private final MemberLikeRepository memberLikeRepository;
private final MinioClient minioClient;

public static final int IMAGE_LIMIT = 10;

// 월별 기록 조회
@Transactional(readOnly = true)
@Override
Expand All @@ -58,21 +61,19 @@ public HistoryResponseDTO.MonthlyHistoryPreview getMonthlyPreview(String clokeyI
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_MEMBER));
}

// 기록 조회
// 기록 조회, 리스트가 비어있다면 빈 리스트 반환
List<History> histories = historyRepository.findHistoriesByMemberIdAndYearMonth(member.getId(), date);
if (histories.isEmpty()) {
throw new HistoryException(ErrorStatus.NO_SUCH_HISTORY);
return HistoryConverter.toMonthlyHistoryPreview(member, Collections.emptyList(), Collections.emptyMap());
}


List<Long> historyIds = histories.stream()
.map(History::getId)
.collect(Collectors.toList());

// 사진 조회
List<HistoryImage> historyImages = historyImageRepository.findAllByHistoryIdIn(historyIds);

// historyId → 첫 번째 이미지 URL
Map<Long, String> firstImagesOfHistory = historyImages.stream()
.collect(Collectors.groupingBy(
img -> img.getHistory().getId(),
Expand All @@ -82,15 +83,7 @@ public HistoryResponseDTO.MonthlyHistoryPreview getMonthlyPreview(String clokeyI
))
));

List<HistoryResponseDTO.MonthlyHistoryItemResult> resultList = histories.stream()
.map(history -> HistoryResponseDTO.MonthlyHistoryItemResult.builder()
.historyId(history.getId())
.date(history.getHistoryDate().toString())
.imageUrl(firstImagesOfHistory.getOrDefault(history.getId(), "비공개입니다"))
.build())
.collect(Collectors.toList());

return HistoryConverter.toMonthlyHistoryPreview(member, resultList);
return HistoryConverter.toMonthlyHistoryPreview(member, histories, firstImagesOfHistory);
}

// 일별 기록 조회
Expand Down Expand Up @@ -126,16 +119,7 @@ public HistoryResponseDTO.DailyHistoryPreview getDailyPreview(Long historyId) {

List<Cloth> cloths = clothRepository.findByMemberId(member.getId());

List<HistoryResponseDTO.DailyHistoryClothesPreview> clothPreviews = cloths.stream()
.map(cloth -> HistoryResponseDTO.DailyHistoryClothesPreview.builder()
.clothId(cloth.getId())
.clothImageUrl(cloth.getClothUrl()) // 이미지 필드 맞게 수정
.clothName(cloth.getName())
.build())
.toList();


return HistoryConverter.toDailyHistoryPreview(member, history, images, hashtagNameList, commentCount, clothPreviews);
return HistoryConverter.toDailyHistoryPreview(member, history, images, hashtagNameList, commentCount, cloths);
}

// 날짜별 옷 기록 추가
Expand All @@ -144,7 +128,7 @@ public HistoryResponseDTO.DailyHistoryPreview getDailyPreview(Long historyId) {
public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.HistoryCreateRequest historyCreateRequest, List<MultipartFile> image) {

// 이미지 업로드 개수 제한
if (image.size() >= 10) {
if (image.size() >= IMAGE_LIMIT) {
throw new HistoryException(ErrorStatus.TOO_MANY_IMAGES);
}

Expand All @@ -164,15 +148,12 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi
// clothId 검증
List<Long> requestedIds = historyCreateRequest.getClothes();

for (Long id : requestedIds) {
boolean exists = clothRepository.existsById(id);
if (!exists) {
throw new HistoryException(ErrorStatus.NO_SUCH_CLOTH);
}
}
// DB에서 요청된 clothId 전부 가져옴
List<Cloth> clothes = clothRepository.findAllByIdsAndMemberId(requestedIds, member.getId());

// ClothId 기록
List<Cloth> clothes = clothRepository.findAllById(requestedIds);
if (clothes.size() != requestedIds.size()) {
throw new HistoryException(ErrorStatus.NO_SUCH_CLOTH);
}

List<HistoryCloth> historyClothes = clothes.stream()
.map(cloth -> HistoryCloth.builder()
Expand Down Expand Up @@ -261,7 +242,7 @@ public HistoryResponseDTO.HistoryCreateResult createHistory(HistoryRequestDTO.Hi
@Override
public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.HistoryUpdateRequest request, List<MultipartFile> images, Long historyId) {

if (images.size() >= 10) {
if (images.size() >= IMAGE_LIMIT) {
throw new HistoryException(ErrorStatus.TOO_MANY_IMAGES);
}

Expand All @@ -281,7 +262,12 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi

// 새로운 clothes 저장
List<Long> clothIds = request.getClothes();
List<Cloth> clothes = clothRepository.findAllById(clothIds);

List<Cloth> clothes = clothRepository.findAllByIdsAndMemberId(clothIds, member.getId());

if (clothes.size() != clothIds.size()) {
throw new HistoryException(ErrorStatus.NO_SUCH_CLOTH);
}

List<HistoryCloth> historyClothes = clothes.stream()
.map(cloth -> HistoryCloth.builder()
Expand Down Expand Up @@ -332,13 +318,13 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi
try {
objectName = url.substring(url.lastIndexOf("/") + 1);
} catch (Exception ex) {
System.err.println("URL에서 objectName 추출 실패: " + url);
System.err.println("URL에서 objectName 추출 실패: " + url);
continue;
}

// MinIO에서 삭제 시도
try {
System.out.println("🗑️ MinIO 삭제 시도: " + objectName);
System.out.println("MinIO 삭제 시도: " + objectName);

minioClient.removeObject(
RemoveObjectArgs.builder()
Expand All @@ -347,9 +333,9 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi
.build()
);

System.out.println("삭제 성공: " + objectName);
System.out.println("삭제 성공: " + objectName);
} catch (Exception e) {
System.err.println("삭제 실패: " + objectName + ", 이유: " + e.getMessage());
System.err.println("삭제 실패: " + objectName + ", 이유: " + e.getMessage());
throw new HistoryException(ErrorStatus.MINIO_DELETE_FAILED); // 정의 필요
}
}
Expand All @@ -358,6 +344,7 @@ public HistoryResponseDTO.HistoryUpdateResult updateHistory(HistoryRequestDTO.Hi
// DB에서 HistoryImage 삭제
historyImageRepository.deleteAllByHistory(history);

// 중복
// 새 이미지 업로드 및 저장
for (MultipartFile file : images) {
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Expand Down Expand Up @@ -430,4 +417,74 @@ public void deleteHistory(Long historyId) {
// 해당 기록 삭제
historyRepository.delete(history);
}
}

// 좋아요 추가 및 삭제
@Transactional
@Override
public HistoryResponseDTO.HistoryLikeResult likeHistory(Long historyId, boolean isLiked) {

// 로그인 된 계정
Member member = memberRepository.findById(1L)
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_MEMBER));

History history = historyRepository.findById(historyId)
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_HISTORY));

if (isLiked) {
history.decreaseLikes();
memberLikeRepository.deleteByMemberIdAndHistoryId(member.getId(), historyId);
} else {
history.increaseLikes();

MemberLike memberLike = MemberLike.builder()
.member(member)
.history(history)
.build();

memberLikeRepository.save(memberLike);
}

return HistoryConverter.toHistoryLikeResult(history, isLiked);
}

@Transactional
@Override
public HistoryResponseDTO.CommentWriteResult writeComment(Long historyId, Long parentCommentId, String content) {

Member member = memberRepository.findById(1L)
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_MEMBER));

History history = historyRepository.findById(historyId)
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_HISTORY));

Comment parentComment = null;

// 상위 댓글이 존재하는지 확인
if (parentCommentId != null) {
parentComment = commentRepository.findById(parentCommentId)
.orElseThrow(() -> new HistoryException(ErrorStatus.NO_SUCH_COMMENT));

// 대댓글까지만 가능하도록 처리
if (parentComment.getComment() != null) {
throw new HistoryException(ErrorStatus.TOO_DEEP_REPLY);
}

// 같은 히스토리인지 확인
if (!parentComment.getHistory().getId().equals(historyId)) {
throw new HistoryException(ErrorStatus.PARENT_COMMENT_HISTORY);
}
}

// 댓글 엔티티 생성
Comment comment = Comment.builder()
.content(content)
.comment(parentComment) // null이면 일반 댓글, 아니면 대댓글
.history(history)
.member(member)
.build();

Comment result = commentRepository.save(comment);

return HistoryConverter.toCommentWriteResult(result);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package study.goorm.domain.history.converter;

import study.goorm.domain.cloth.domain.entity.Cloth;
import study.goorm.domain.history.domain.entity.Comment;
import study.goorm.domain.history.domain.entity.Hashtag;
import study.goorm.domain.history.domain.entity.History;
import study.goorm.domain.history.domain.entity.HistoryImage;
Expand All @@ -9,15 +10,28 @@

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class HistoryConverter {

public static HistoryResponseDTO.MonthlyHistoryPreview toMonthlyHistoryPreview(Member member, List<HistoryResponseDTO.MonthlyHistoryItemResult> result) {
// 기록
public static HistoryResponseDTO.MonthlyHistoryPreview toMonthlyHistoryPreview (
Member member,
List<History> histories,
Map<Long, String> firstImagesOfHistory) {

return HistoryResponseDTO.MonthlyHistoryPreview.builder()
.memberId(member.getId())
.nickName(member.getNickname())
.histories(result)
.histories(
histories.stream()
.map(history -> HistoryResponseDTO.MonthlyHistoryItemResult.builder()
.historyId(history.getId())
.date(history.getHistoryDate().toString())
.imageUrl(firstImagesOfHistory.getOrDefault(history.getId(), "비공개입니다"))
.build())
.collect(Collectors.toList())
)
.build();
}

Expand All @@ -27,7 +41,7 @@ public static HistoryResponseDTO.DailyHistoryPreview toDailyHistoryPreview(
List<String> images,
List<String> hashtags,
long commentCount,
List<HistoryResponseDTO.DailyHistoryClothesPreview> cloths
List<Cloth> cloths
) {

return HistoryResponseDTO.DailyHistoryPreview.builder()
Expand All @@ -42,7 +56,15 @@ public static HistoryResponseDTO.DailyHistoryPreview toDailyHistoryPreview(
.likeCount(history.getLikes())
.commentCount(commentCount)
.date(history.getHistoryDate())
.cloths(cloths)
.cloths(
cloths.stream()
.map(cloth -> HistoryResponseDTO.DailyHistoryClothesPreview.builder()
.clothId(cloth.getId())
.clothImageUrl(cloth.getClothUrl())
.clothName(cloth.getName())
.build())
.collect(Collectors.toList())
)
.build();
}

Expand All @@ -57,4 +79,20 @@ 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())
.isLiked(isLiked)
.likeCount(history.getLikes())
.build();
}

// 댓글 작성
public static HistoryResponseDTO.CommentWriteResult toCommentWriteResult(Comment comment) {
return HistoryResponseDTO.CommentWriteResult.builder()
.commentId(comment.getId())
.build();
}
}
Loading