diff --git a/.gitignore b/.gitignore index 55608a8..baa104d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ out/ ### VS Code ### .vscode/ + +.env \ No newline at end of file diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/controller/AuthController.java b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/controller/AuthController.java index 25ae0cc..d8bd267 100644 --- a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/controller/AuthController.java +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/controller/AuthController.java @@ -3,7 +3,7 @@ import com.cokerthon.Team3_Backend.domain.auth.dto.request.LoginRequest; import com.cokerthon.Team3_Backend.domain.auth.dto.request.SignupRequest; import com.cokerthon.Team3_Backend.domain.auth.dto.response.AuthResponse; -import com.cokerthon.Team3_Backend.domain.auth.exception.AuthSuccessCode; +import com.cokerthon.Team3_Backend.domain.auth.exception.code.AuthSuccessCode; import com.cokerthon.Team3_Backend.domain.auth.service.AuthService; import com.cokerthon.Team3_Backend.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthErrorCode.java b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthErrorCode.java similarity index 92% rename from src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthErrorCode.java rename to src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthErrorCode.java index 8124bb4..196af80 100644 --- a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthErrorCode.java +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthErrorCode.java @@ -1,10 +1,11 @@ -package com.cokerthon.Team3_Backend.domain.auth.exception; +package com.cokerthon.Team3_Backend.domain.auth.exception.code; -import com.cokerthon.Team3_Backend.global.apiPayload.code.BaseErrorCode; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import com.cokerthon.Team3_Backend.global.apiPayload.code.BaseErrorCode; + @Getter @AllArgsConstructor public enum AuthErrorCode implements BaseErrorCode { diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthSuccessCode.java b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthSuccessCode.java similarity index 90% rename from src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthSuccessCode.java rename to src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthSuccessCode.java index 6222102..e8a9c32 100644 --- a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/AuthSuccessCode.java +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/exception/code/AuthSuccessCode.java @@ -1,10 +1,11 @@ -package com.cokerthon.Team3_Backend.domain.auth.exception; +package com.cokerthon.Team3_Backend.domain.auth.exception.code; -import com.cokerthon.Team3_Backend.global.apiPayload.code.BaseSuccessCode; import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import com.cokerthon.Team3_Backend.global.apiPayload.code.BaseSuccessCode; + @Getter @AllArgsConstructor public enum AuthSuccessCode implements BaseSuccessCode { diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/service/AuthService.java b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/service/AuthService.java index 5689caa..5db8794 100644 --- a/src/main/java/com/cokerthon/Team3_Backend/domain/auth/service/AuthService.java +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/auth/service/AuthService.java @@ -3,7 +3,7 @@ import com.cokerthon.Team3_Backend.domain.auth.dto.request.LoginRequest; import com.cokerthon.Team3_Backend.domain.auth.dto.request.SignupRequest; import com.cokerthon.Team3_Backend.domain.auth.dto.response.AuthResponse; -import com.cokerthon.Team3_Backend.domain.auth.exception.AuthErrorCode; +import com.cokerthon.Team3_Backend.domain.auth.exception.code.AuthErrorCode; import com.cokerthon.Team3_Backend.domain.auth.exception.AuthException; import com.cokerthon.Team3_Backend.domain.user.entity.User; import com.cokerthon.Team3_Backend.domain.user.repository.UserRepository; diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/community/controller/CommunityController.java b/src/main/java/com/cokerthon/Team3_Backend/domain/community/controller/CommunityController.java new file mode 100644 index 0000000..0f369d0 --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/community/controller/CommunityController.java @@ -0,0 +1,47 @@ +package com.cokerthon.Team3_Backend.domain.community.controller; + +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.cokerthon.Team3_Backend.domain.community.dto.response.CommunityResDto; +import com.cokerthon.Team3_Backend.domain.community.service.CommunityService; +import com.cokerthon.Team3_Backend.domain.emotion.dto.response.LikeResDto; +import com.cokerthon.Team3_Backend.domain.emotion.service.EmotionLikeService; +import com.cokerthon.Team3_Backend.domain.user.entity.User; +import com.cokerthon.Team3_Backend.global.apiPayload.ApiResponse; +import com.cokerthon.Team3_Backend.global.apiPayload.code.GeneralSuccessCode; +import com.cokerthon.Team3_Backend.global.security.CurrentUser; + +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/community") +@RequiredArgsConstructor +public class CommunityController { + + private final CommunityService communityService; + private final EmotionLikeService emotionLikeService; + + @GetMapping + @Operation(summary = "공개 감정 글 목록 조회", description = "공개(PUBLIC)로 설정된 모든 사용자의 감정 글을 최신순으로 조회") + public ApiResponse> getCommunityList() { + List response = communityService.getCommunityList(); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, response); + } + + @PostMapping("/like") + @Operation(summary = "힘내요 토글", description = "emotionId를 파라미터로 받아 힘내요를 등록하거나 취소합니다.") + public ApiResponse toggleLike( + @RequestParam(name = "emotionId") Long emotionId, + @CurrentUser User user + ) { + LikeResDto result = emotionLikeService.toggleLike(user, emotionId); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, result); + } +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/community/dto/response/CommunityResDto.java b/src/main/java/com/cokerthon/Team3_Backend/domain/community/dto/response/CommunityResDto.java new file mode 100644 index 0000000..38c60f7 --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/community/dto/response/CommunityResDto.java @@ -0,0 +1,32 @@ +package com.cokerthon.Team3_Backend.domain.community.dto.response; + +import java.time.LocalDateTime; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +@Builder +@Schema(description = "커뮤니티 조회 응답 DTO") +public record CommunityResDto( + + @Schema(description = "감정 글 ID") + @NotNull + Long id, + + @Schema(description = "작성자 닉네임") + String nickname, + + @Schema(description = "감정 점수") + int emotionScore, + + @Schema(description = "감정 글 본문") + String content, + + @Schema(description = "힘내요 수") + long likeCount, + + @Schema(description = "작성 시간") + LocalDateTime createdAt +) { +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/community/service/CommunityService.java b/src/main/java/com/cokerthon/Team3_Backend/domain/community/service/CommunityService.java new file mode 100644 index 0000000..ea4047d --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/community/service/CommunityService.java @@ -0,0 +1,24 @@ +package com.cokerthon.Team3_Backend.domain.community.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.cokerthon.Team3_Backend.domain.community.dto.response.CommunityResDto; +import com.cokerthon.Team3_Backend.domain.emotion.repository.EmotionRecordRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class CommunityService { + + private final EmotionRecordRepository emotionRecordRepository; + + @Transactional(readOnly = true) + public List getCommunityList() { + return emotionRecordRepository.findAllCommunityPosts(); + } + +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/converter/EmotionLikeConverter.java b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/converter/EmotionLikeConverter.java new file mode 100644 index 0000000..9217ad8 --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/converter/EmotionLikeConverter.java @@ -0,0 +1,18 @@ +package com.cokerthon.Team3_Backend.domain.emotion.converter; + +import com.cokerthon.Team3_Backend.domain.emotion.dto.response.LikeResDto; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class EmotionLikeConverter { + + public static LikeResDto toLikeResDto(Long emotionId, int currentLikeCount, boolean isLiked) { + return LikeResDto.builder() + .id(emotionId) + .likeCount(currentLikeCount) + .like(isLiked) + .build(); + } + +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/dto/response/LikeResDto.java b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/dto/response/LikeResDto.java new file mode 100644 index 0000000..9a5d48a --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/dto/response/LikeResDto.java @@ -0,0 +1,19 @@ +package com.cokerthon.Team3_Backend.domain.emotion.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; + +@Builder +@Schema(description = "감정 글 힘내요 응답 DTO") +public record LikeResDto( + + @Schema(description = "게시글 ID") + Long id, + + @Schema(description = "힘내요 수") + int likeCount, + + @Schema(description = "힘내요 여부") + boolean like +) { +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionLikeRepository.java b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionLikeRepository.java new file mode 100644 index 0000000..f84836c --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionLikeRepository.java @@ -0,0 +1,16 @@ +package com.cokerthon.Team3_Backend.domain.emotion.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.cokerthon.Team3_Backend.domain.emotion.entity.EmotionLike; +import com.cokerthon.Team3_Backend.domain.emotion.entity.EmotionRecord; +import com.cokerthon.Team3_Backend.domain.user.entity.User; + +public interface EmotionLikeRepository extends JpaRepository { + + long countByEmotion(EmotionRecord emotion); + + Optional findByUserAndEmotion(User user, EmotionRecord emotion); +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionRecordRepository.java b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionRecordRepository.java new file mode 100644 index 0000000..05d7e43 --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/repository/EmotionRecordRepository.java @@ -0,0 +1,22 @@ +package com.cokerthon.Team3_Backend.domain.emotion.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import com.cokerthon.Team3_Backend.domain.community.dto.response.CommunityResDto; +import com.cokerthon.Team3_Backend.domain.emotion.entity.EmotionRecord; + +public interface EmotionRecordRepository extends JpaRepository { + + @Query("SELECT new com.cokerthon.Team3_Backend.domain.community.dto.response.CommunityResDto(" + + "r.id, u.nickname, r.emotionScore, r.content, COUNT(l), r.createdAt) " + + "FROM EmotionRecord r " + + "JOIN r.user u " + + "LEFT JOIN EmotionLike l ON l.emotion = r " + + "WHERE r.visibility = com.cokerthon.Team3_Backend.domain.emotion.enums.Visibility.PUBLIC " + // Visiblity -> Visibility 로 수정 + "GROUP BY r.id, u.nickname, r.emotionScore, r.content, r.createdAt " + + "ORDER BY r.createdAt DESC") + List findAllCommunityPosts(); +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/service/EmotionLikeService.java b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/service/EmotionLikeService.java new file mode 100644 index 0000000..3ad057a --- /dev/null +++ b/src/main/java/com/cokerthon/Team3_Backend/domain/emotion/service/EmotionLikeService.java @@ -0,0 +1,54 @@ +package com.cokerthon.Team3_Backend.domain.emotion.service; + +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.cokerthon.Team3_Backend.domain.emotion.converter.EmotionLikeConverter; +import com.cokerthon.Team3_Backend.domain.emotion.dto.response.LikeResDto; +import com.cokerthon.Team3_Backend.domain.emotion.entity.EmotionLike; +import com.cokerthon.Team3_Backend.domain.emotion.entity.EmotionRecord; +import com.cokerthon.Team3_Backend.domain.emotion.exception.EmotionException; +import com.cokerthon.Team3_Backend.domain.emotion.exception.code.EmotionErrorCode; +import com.cokerthon.Team3_Backend.domain.emotion.repository.EmotionLikeRepository; +import com.cokerthon.Team3_Backend.domain.emotion.repository.EmotionRecordRepository; +import com.cokerthon.Team3_Backend.domain.user.entity.User; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class EmotionLikeService { + + private final EmotionRecordRepository emotionRecordRepository; + private final EmotionLikeRepository emotionLikeRepository; + + @Transactional + public LikeResDto toggleLike(User user, Long emotionId) { + EmotionRecord emotion = emotionRecordRepository.findById(emotionId) + .orElseThrow(() -> new EmotionException(EmotionErrorCode.EMOTION_NOT_FOUND)); + + Optional existingLike = emotionLikeRepository.findByUserAndEmotion(user, emotion); + boolean isLiked; + + if (existingLike.isPresent()) { + emotionLikeRepository.delete(existingLike.get()); + isLiked = false; + } else { + EmotionLike newLike = EmotionLike.builder() + .user(user) + .emotion(emotion) + .createdAt(LocalDateTime.now()) + .build(); + emotionLikeRepository.save(newLike); + isLiked = true; + } + + int currentLikeCount = (int) emotionLikeRepository.countByEmotion(emotion); + + return EmotionLikeConverter.toLikeResDto(emotionId, currentLikeCount, isLiked); + } + +} diff --git a/src/main/java/com/cokerthon/Team3_Backend/global/security/CustomUserDetailsService.java b/src/main/java/com/cokerthon/Team3_Backend/global/security/CustomUserDetailsService.java index 4dc7144..a7c6799 100644 --- a/src/main/java/com/cokerthon/Team3_Backend/global/security/CustomUserDetailsService.java +++ b/src/main/java/com/cokerthon/Team3_Backend/global/security/CustomUserDetailsService.java @@ -1,6 +1,6 @@ package com.cokerthon.Team3_Backend.global.security; -import com.cokerthon.Team3_Backend.domain.auth.exception.AuthErrorCode; +import com.cokerthon.Team3_Backend.domain.auth.exception.code.AuthErrorCode; import com.cokerthon.Team3_Backend.domain.auth.exception.AuthException; import com.cokerthon.Team3_Backend.domain.user.entity.User; import com.cokerthon.Team3_Backend.domain.user.repository.UserRepository;