diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml deleted file mode 100644 index bde6588..0000000 --- a/.idea/dbnavigator.xml +++ /dev/null @@ -1,531 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
\ No newline at end of file diff --git a/src/main/java/dongguk/osori/domain/goal/GoalRepository.java b/src/main/java/dongguk/osori/domain/goal/GoalRepository.java deleted file mode 100644 index e4c8c47..0000000 --- a/src/main/java/dongguk/osori/domain/goal/GoalRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package dongguk.osori.domain.goal; - -import dongguk.osori.domain.goal.entity.Goal; -import dongguk.osori.domain.user.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface GoalRepository extends JpaRepository { - Optional findByGoalIdAndUser(Long goalId, User user); -} diff --git a/src/main/java/dongguk/osori/domain/goal/controller/FeedController.java b/src/main/java/dongguk/osori/domain/goal/controller/FeedController.java new file mode 100644 index 0000000..eb3388d --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/controller/FeedController.java @@ -0,0 +1,50 @@ +package dongguk.osori.domain.goal.controller; + +import dongguk.osori.domain.goal.dto.FeedGoalDto; +import dongguk.osori.domain.goal.dto.GoalDetailResponseDto; +import dongguk.osori.domain.goal.service.GoalService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/feeds") +public class FeedController { + + private final GoalService goalService; + + @Operation(summary = "팔로우한 사람들의 오늘 날짜 목표 조회", description = "팔로우한 사람들의 오늘 날짜 목표를 피드에서 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "목표 조회 성공"), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자") + }) + @GetMapping + public ResponseEntity> getTodayFeedGoals(HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + List feedGoals = goalService.getTodayFeedGoals(userId); + return ResponseEntity.ok(feedGoals); + } + + @Operation(summary = "단일 목표 상세 조회", description = "단일 목표와 그에 달린 모든 댓글을 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "목표 상세 조회 성공"), + @ApiResponse(responseCode = "404", description = "목표를 찾을 수 없음") + }) + @GetMapping("/{goalId}") + public ResponseEntity getGoalDetailsWithComments(@PathVariable("goalId") Long goalId) { + Optional goalDetails = goalService.getGoalDetailsWithComments(goalId); + return goalDetails.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } +} diff --git a/src/main/java/dongguk/osori/domain/goal/controller/GoalCommentController.java b/src/main/java/dongguk/osori/domain/goal/controller/GoalCommentController.java new file mode 100644 index 0000000..fc7e3e6 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/controller/GoalCommentController.java @@ -0,0 +1,41 @@ +package dongguk.osori.domain.goal.controller; + +import dongguk.osori.domain.goal.dto.GoalCommentDto; +import dongguk.osori.domain.goal.dto.GoalCommentResponseDto; +import dongguk.osori.domain.goal.service.GoalCommentService; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/goals/{goalId}/comments") +@RequiredArgsConstructor +public class GoalCommentController { + + private final GoalCommentService goalCommentService; + + // 댓글 추가 + @PostMapping + public ResponseEntity addComment( + @PathVariable("goalId") Long goalId, + @RequestBody GoalCommentDto commentDto, + HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + GoalCommentResponseDto response = goalCommentService.addComment(goalId, userId, commentDto); + return ResponseEntity.ok(response); + } + + // 댓글 삭제 + @DeleteMapping("/{commentId}") + public ResponseEntity deleteComment( + @PathVariable("goalId") Long goalId, + @PathVariable("commentId") Long commentId, + @RequestAttribute("userId") Long userId) { + goalCommentService.deleteComment(commentId, userId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/dongguk/osori/domain/goal/GoalController.java b/src/main/java/dongguk/osori/domain/goal/controller/GoalController.java similarity index 67% rename from src/main/java/dongguk/osori/domain/goal/GoalController.java rename to src/main/java/dongguk/osori/domain/goal/controller/GoalController.java index 1e1608b..ea21dd2 100644 --- a/src/main/java/dongguk/osori/domain/goal/GoalController.java +++ b/src/main/java/dongguk/osori/domain/goal/controller/GoalController.java @@ -1,8 +1,6 @@ -package dongguk.osori.domain.goal; +package dongguk.osori.domain.goal.controller; -import dongguk.osori.domain.goal.dto.GoalCompletionDto; -import dongguk.osori.domain.goal.dto.GoalDto; -import dongguk.osori.domain.goal.entity.Goal; +import dongguk.osori.domain.goal.dto.*; import dongguk.osori.domain.goal.service.GoalService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -31,27 +29,45 @@ public class GoalController { @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자") }) @GetMapping - public ResponseEntity> getUserGoals(HttpSession session) { + public ResponseEntity> getUserGoals(HttpSession session) { Long userId = (Long) session.getAttribute("userId"); if (userId == null) { return ResponseEntity.status(401).build(); } - List goals = goalService.getUserGoals(userId); + List goals = goalService.getUserGoals(userId); return ResponseEntity.ok(goals); } + @Operation(summary = "목표 단일 조회", description = "특정 목표의 내용을 조회하고 연관된 댓글을 함께 보여줍니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "목표 조회 성공"), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"), + @ApiResponse(responseCode = "404", description = "목표를 찾을 수 없음") + }) + @GetMapping("/{goalId}") + public ResponseEntity getGoalById(@PathVariable("goalId") Long goalId, HttpSession session) { + Long userId = (Long) session.getAttribute("userId"); + if (userId == null) { + return ResponseEntity.status(401).build(); + } + Optional goalDetail = goalService.getGoalDetailsWithComments(goalId); + return goalDetail.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + @Operation(summary = "로그인한 사용자의 목표 생성", description = "로그인한 사용자의 새로운 목표를 생성합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "목표 생성 성공"), @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자") }) @PostMapping - public ResponseEntity createGoal(@RequestBody GoalDto goalDto, HttpSession session) { + public ResponseEntity createGoal(@RequestBody GoalDto goalDto, HttpSession session) { Long userId = (Long) session.getAttribute("userId"); if (userId == null) { return ResponseEntity.status(401).build(); } - Goal createdGoal = goalService.createGoal(goalDto, userId); + GoalResponseDto createdGoal = goalService.createGoal(goalDto, userId); return ResponseEntity.ok(createdGoal); } @@ -62,12 +78,12 @@ public ResponseEntity createGoal(@RequestBody GoalDto goalDto, HttpSession @ApiResponse(responseCode = "404", description = "목표를 찾을 수 없음") }) @PatchMapping("/{goalId}") - public ResponseEntity updateGoal(@PathVariable("goalId") Long goalId, @RequestBody GoalDto goalDto, HttpSession session) { + public ResponseEntity updateGoal(@PathVariable("goalId") Long goalId, @RequestBody GoalDto goalDto, HttpSession session) { Long userId = (Long) session.getAttribute("userId"); if (userId == null) { return ResponseEntity.status(401).build(); } - Optional updatedGoal = goalService.updateGoal(goalId, goalDto, userId); + Optional updatedGoal = goalService.updateGoal(goalId, goalDto, userId); return updatedGoal.map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } @@ -83,7 +99,7 @@ public ResponseEntity deleteGoal(@PathVariable("goalId") Long goalId, Http if (userId == null) { return ResponseEntity.status(401).build(); } - goalService.deleteGoal(goalId, userId); + goalService.deleteGoal(userId, goalId); return ResponseEntity.noContent().build(); } @@ -94,12 +110,12 @@ public ResponseEntity deleteGoal(@PathVariable("goalId") Long goalId, Http @ApiResponse(responseCode = "404", description = "목표를 찾을 수 없음") }) @PatchMapping("/{goalId}/completion") - public ResponseEntity updateGoalCompletion(@PathVariable("goalId") Long goalId, @RequestBody GoalCompletionDto goalCompletionDto, HttpSession session) { + public ResponseEntity updateGoalCompletion(@PathVariable("goalId") Long goalId, @RequestBody GoalCompletionDto goalCompletionDto, HttpSession session) { Long userId = (Long) session.getAttribute("userId"); if (userId == null) { return ResponseEntity.status(401).build(); } - Optional updatedGoal = goalService.updateGoalCompletionStatus(goalId, goalCompletionDto, userId); + Optional updatedGoal = goalService.updateGoalCompletionStatus(goalId, goalCompletionDto, userId); return updatedGoal.map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } diff --git a/src/main/java/dongguk/osori/domain/goal/dto/FeedGoalDto.java b/src/main/java/dongguk/osori/domain/goal/dto/FeedGoalDto.java new file mode 100644 index 0000000..5275161 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/FeedGoalDto.java @@ -0,0 +1,17 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class FeedGoalDto { + private Long goalId; + private String authorNickname; + private LocalDateTime createdAt; + private boolean completed; + private String content; + private int commentCount; +} diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentDto.java new file mode 100644 index 0000000..4088121 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentDto.java @@ -0,0 +1,18 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GoalCommentDto { + private Long commentId; + private String nickname; + private String content; + private LocalDateTime createdAt; + private int emoji; +} diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentResponseDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentResponseDto.java new file mode 100644 index 0000000..8581fd5 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalCommentResponseDto.java @@ -0,0 +1,17 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +@AllArgsConstructor +public class GoalCommentResponseDto { + private Long commentId; + private String nickname; + private String content; + private LocalDateTime createdAt; + private int emoji; +} + diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalDetailResponseDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalDetailResponseDto.java new file mode 100644 index 0000000..d5867dd --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalDetailResponseDto.java @@ -0,0 +1,18 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@AllArgsConstructor +public class GoalDetailResponseDto { + private Long goalId; + private String content; + private String nickname; + private LocalDateTime createdAt; + private boolean completed; + private List comments; +} diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java index 1894437..447c9fe 100644 --- a/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalDto.java @@ -8,5 +8,5 @@ @AllArgsConstructor @NoArgsConstructor public class GoalDto { - private String context; + private String content; } diff --git a/src/main/java/dongguk/osori/domain/goal/dto/GoalResponseDto.java b/src/main/java/dongguk/osori/domain/goal/dto/GoalResponseDto.java new file mode 100644 index 0000000..527a094 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/dto/GoalResponseDto.java @@ -0,0 +1,17 @@ +package dongguk.osori.domain.goal.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GoalResponseDto { + private Long goalId; + private String content; + private LocalDateTime createdAt; + private boolean completed; +} diff --git a/src/main/java/dongguk/osori/domain/goal/entity/Goal.java b/src/main/java/dongguk/osori/domain/goal/entity/Goal.java index 969f412..fd6e60c 100644 --- a/src/main/java/dongguk/osori/domain/goal/entity/Goal.java +++ b/src/main/java/dongguk/osori/domain/goal/entity/Goal.java @@ -6,6 +6,8 @@ import lombok.*; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @AllArgsConstructor @NoArgsConstructor @@ -14,11 +16,12 @@ @Entity public class Goal { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long goalId; @Column(nullable = false) - private String context; + private String content; @Column(nullable = false) private LocalDateTime createdAt; @@ -31,10 +34,20 @@ public class Goal { @JsonIgnore private User user; + @OneToMany(mappedBy = "goal", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); - public void updateContext(String context) { this.context = context; } - public void markAsCompleted() { this.completed = true; } - public void markAsIncomplete() { this.completed = false; } + public void updateContent(String content) { + this.content = content; + } + + public void markAsCompleted() { + this.completed = true; + } + + public void markAsIncomplete() { + this.completed = false; + } @PrePersist public void prePersist() { @@ -43,9 +56,10 @@ public void prePersist() { } @Builder - public Goal(String context, User user) { - this.context = context; + public Goal(String content, User user) { + this.content = content; this.user = user; + this.createdAt = LocalDateTime.now(); + this.completed = false; } - } diff --git a/src/main/java/dongguk/osori/domain/goal/entity/GoalComment.java b/src/main/java/dongguk/osori/domain/goal/entity/GoalComment.java new file mode 100644 index 0000000..53a91f3 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/entity/GoalComment.java @@ -0,0 +1,71 @@ +package dongguk.osori.domain.goal.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import dongguk.osori.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Entity +public class GoalComment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long commentId; + + @Column(nullable = false) + private String content; + + @Column(nullable = false) + private int emoji; + + @Column(nullable = false) + private LocalDateTime createdAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "goal_id") + @JsonIgnore + private Goal goal; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + @JsonIgnore + private User user; + + @Builder + public GoalComment(String content, int emoji, Goal goal, User user) { + this.content = content; + this.emoji = emoji; + this.goal = goal; + this.user = user; + this.createdAt = LocalDateTime.now(); + } + + public void updateCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public void updateCommentId(Long commentId) { + this.commentId = commentId; + } + + public void updateContent(String content) { + this.content = content; + } + + public void updateEmoji(int emoji) { + this.emoji = emoji; + } + + public void updateGoal(Goal goal) { + this.goal = goal; + } + + public void updateUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/dongguk/osori/domain/goal/repository/GoalCommentRepository.java b/src/main/java/dongguk/osori/domain/goal/repository/GoalCommentRepository.java new file mode 100644 index 0000000..178e6a4 --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/repository/GoalCommentRepository.java @@ -0,0 +1,12 @@ +package dongguk.osori.domain.goal.repository; + +import dongguk.osori.domain.goal.entity.GoalComment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface GoalCommentRepository extends JpaRepository { + List findByGoal_GoalId(Long goalId); +} diff --git a/src/main/java/dongguk/osori/domain/goal/repository/GoalRepository.java b/src/main/java/dongguk/osori/domain/goal/repository/GoalRepository.java new file mode 100644 index 0000000..ce1be2d --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/repository/GoalRepository.java @@ -0,0 +1,19 @@ +package dongguk.osori.domain.goal.repository; + +import dongguk.osori.domain.goal.entity.Goal; +import dongguk.osori.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Repository +public interface GoalRepository extends JpaRepository { + Optional findByGoalId(Long goalId); + Optional findByGoalIdAndUser(Long goalId, User user); + List findByUserInAndCreatedAtBetween(List users, LocalDateTime start, LocalDateTime end); + +} + diff --git a/src/main/java/dongguk/osori/domain/goal/service/GoalCommentService.java b/src/main/java/dongguk/osori/domain/goal/service/GoalCommentService.java new file mode 100644 index 0000000..a724d9a --- /dev/null +++ b/src/main/java/dongguk/osori/domain/goal/service/GoalCommentService.java @@ -0,0 +1,96 @@ +package dongguk.osori.domain.goal.service; + +import dongguk.osori.domain.goal.dto.GoalCommentDto; +import dongguk.osori.domain.goal.dto.GoalCommentResponseDto; +import dongguk.osori.domain.goal.dto.GoalDetailResponseDto; +import dongguk.osori.domain.goal.entity.Goal; +import dongguk.osori.domain.goal.entity.GoalComment; +import dongguk.osori.domain.goal.repository.GoalCommentRepository; +import dongguk.osori.domain.goal.repository.GoalRepository; +import dongguk.osori.domain.user.entity.User; +import dongguk.osori.domain.user.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class GoalCommentService { + + private final GoalCommentRepository goalCommentRepository; + private final GoalRepository goalRepository; + private final UserRepository userRepository; + + // 댓글 추가 + @Transactional + public GoalCommentResponseDto addComment(Long goalId, Long userId, GoalCommentDto commentDto) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found")); + Goal goal = goalRepository.findById(goalId) + .orElseThrow(() -> new RuntimeException("Goal not found")); + + GoalComment comment = GoalComment.builder() + .content(commentDto.getContent()) + .emoji(commentDto.getEmoji()) + .goal(goal) + .user(user) + .build(); + + goalCommentRepository.save(comment); + + return new GoalCommentResponseDto( + comment.getCommentId(), + user.getNickname(), + comment.getContent(), + comment.getCreatedAt(), + comment.getEmoji() + ); + } + + // 댓글 조회 + @Transactional + public Optional getGoalDetailsWithComments(Long goalId) { + return goalRepository.findById(goalId) + .map(goal -> { + List comments = goal.getComments().stream() + .map(comment -> new GoalCommentResponseDto( + comment.getCommentId(), + comment.getUser().getNickname(), + comment.getContent(), + comment.getCreatedAt(), + comment.getEmoji() + )) + .collect(Collectors.toList()); + + return new GoalDetailResponseDto( + goal.getGoalId(), + goal.getContent(), + goal.getUser().getNickname(), + goal.getCreatedAt(), + goal.isCompleted(), + comments + ); + }); + } + + + // 댓글 삭제 + @Transactional + public void deleteComment(Long commentId, Long userId) { + GoalComment comment = goalCommentRepository.findById(commentId) + .orElseThrow(() -> new RuntimeException("Comment not found")); + + if (!comment.getUser().getUserId().equals(userId)) { + throw new RuntimeException("Unauthorized user"); + } + + goalCommentRepository.deleteById(commentId); + } +} diff --git a/src/main/java/dongguk/osori/domain/goal/service/GoalService.java b/src/main/java/dongguk/osori/domain/goal/service/GoalService.java index 3b67549..f829df4 100644 --- a/src/main/java/dongguk/osori/domain/goal/service/GoalService.java +++ b/src/main/java/dongguk/osori/domain/goal/service/GoalService.java @@ -1,8 +1,9 @@ package dongguk.osori.domain.goal.service; -import dongguk.osori.domain.goal.GoalRepository; -import dongguk.osori.domain.goal.dto.GoalCompletionDto; -import dongguk.osori.domain.goal.dto.GoalDto; +import dongguk.osori.domain.goal.dto.*; +import dongguk.osori.domain.goal.entity.GoalComment; +import dongguk.osori.domain.goal.repository.GoalCommentRepository; +import dongguk.osori.domain.goal.repository.GoalRepository; import dongguk.osori.domain.goal.entity.Goal; import dongguk.osori.domain.user.entity.User; import dongguk.osori.domain.user.UserRepository; @@ -11,8 +12,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Slf4j @Service @@ -21,6 +25,7 @@ public class GoalService { private final GoalRepository goalRepository; private final UserRepository userRepository; + private final GoalCommentRepository goalCommentRepository; // 로그인된 사용자 가져오기 private User getLoggedInUser(Long userId) { @@ -29,47 +34,47 @@ private User getLoggedInUser(Long userId) { .orElseThrow(() -> new RuntimeException("User not found")); } - // 로그인된 사용자의 모든 목표 조회 - public List getUserGoals(Long userId) { + public List getUserGoals(Long userId) { User user = getLoggedInUser(userId); log.debug("Goals retrieved: {}", user.getGoals()); // 디버깅용 - return user.getGoals(); + return user.getGoals().stream() + .map(goal -> new GoalResponseDto(goal.getGoalId(), goal.getContent(), goal.getCreatedAt(), goal.isCompleted())) + .collect(Collectors.toList()); } // 로그인된 사용자의 목표 생성 @Transactional - public Goal createGoal(GoalDto goalDto, Long userId) { + public GoalResponseDto createGoal(GoalDto goalDto, Long userId) { log.debug("Creating goal for user ID: {}", userId); // 디버깅용 User user = getLoggedInUser(userId); log.debug("User found: {}", user); // 디버깅용 // Goal Builder 사용하여 새로운 목표 생성 Goal goal = Goal.builder() - .context(goalDto.getContext()) + .content(goalDto.getContent()) .user(user) .build(); log.debug("New goal created: {}", goal); // 디버깅용 - user.getGoals().add(goal); - return goalRepository.save(goal); + goalRepository.save(goal); + return new GoalResponseDto(goal.getGoalId(), goal.getContent(), goal.getCreatedAt(), goal.isCompleted()); } // 로그인된 사용자의 목표 수정 @Transactional - public Optional updateGoal(Long goalId, GoalDto goalDto, Long userId) { + public Optional updateGoal(Long goalId, GoalDto goalDto, Long userId) { User user = getLoggedInUser(userId); return goalRepository.findByGoalIdAndUser(goalId, user) .map(goal -> { - goal.updateContext(goalDto.getContext()); - return goal; + goal.updateContent(goalDto.getContent()); + return new GoalResponseDto(goal.getGoalId(), goal.getContent(), goal.getCreatedAt(), goal.isCompleted()); }); } - // 로그인된 사용자의 목표 달성 여부 업데이트 @Transactional - public Optional updateGoalCompletionStatus(Long goalId, GoalCompletionDto goalCompletionDto, Long userId) { + public Optional updateGoalCompletionStatus(Long goalId, GoalCompletionDto goalCompletionDto, Long userId) { User user = getLoggedInUser(userId); return goalRepository.findById(goalId) .filter(goal -> user.getGoals().contains(goal)) @@ -79,7 +84,7 @@ public Optional updateGoalCompletionStatus(Long goalId, GoalCompletionDto } else { goal.markAsIncomplete(); } - return goal; + return new GoalResponseDto(goal.getGoalId(), goal.getContent(), goal.getCreatedAt(), goal.isCompleted()); }); } @@ -88,7 +93,7 @@ public Optional updateGoalCompletionStatus(Long goalId, GoalCompletionDto public String deleteGoal(Long userId, Long goalId) { log.debug("Deleting goal ID: {} for user ID: {}", goalId, userId); // 디버깅용 Goal goal = goalRepository.findById(goalId) - .orElseThrow(() -> new RuntimeException("존재하지 않는 목표")); + .orElseThrow(() -> new RuntimeException("존재하지 않는 목표")); User user = getLoggedInUser(userId); log.debug("User found: {}, Goal user: {}", user, goal.getUser()); // 디버깅용 @@ -97,11 +102,57 @@ public String deleteGoal(Long userId, Long goalId) { log.warn("User mismatch: {} is not the owner of goal ID: {}", userId, goalId); // 디버깅용 return "잘못된 사용자, 삭제 실패"; } - user.getGoals().remove(goal); // User 엔티티에서 해당 목표를 제거 goalRepository.deleteById(goalId); // Goal에서 목표 삭제 log.debug("Goal deleted successfully: {}", goalId); // 디버깅용 - return "아코자국 삭제 완료"; + return "목표 삭제 완료"; } + // 피드에서 오늘 날짜의 팔로우한 사람들의 목표 조회 + @Transactional + public List getTodayFeedGoals(Long userId) { + User loggedInUser = getLoggedInUser(userId); // 로그인된 사용자 가져오기 + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = startOfDay.plusDays(1); + + List followingUsers = loggedInUser.getFollowingUsers(); // 팔로우한 사용자 목록 가져오기 + + // 오늘 날짜에 생성된 팔로우한 사용자의 목표 조회 + return goalRepository.findByUserInAndCreatedAtBetween(followingUsers, startOfDay, endOfDay) + .stream() + .map(goal -> new FeedGoalDto( + goal.getGoalId(), + goal.getUser().getNickname(), + goal.getCreatedAt(), + goal.isCompleted(), + goal.getContent(), + goal.getComments().size() + )) + .collect(Collectors.toList()); + } + // Goal ID를 기준으로 목표 상세 조회 (댓글 포함) + @Transactional + public Optional getGoalDetailsWithComments(Long goalId) { + return goalRepository.findById(goalId) + .map(goal -> { + List comments = goal.getComments().stream() + .map(comment -> new GoalCommentResponseDto( + comment.getCommentId(), + comment.getUser().getNickname(), + comment.getContent(), + comment.getCreatedAt(), + comment.getEmoji() + )) + .collect(Collectors.toList()); + + return new GoalDetailResponseDto( + goal.getGoalId(), + goal.getContent(), + goal.getUser().getNickname(), + goal.getCreatedAt(), + goal.isCompleted(), + comments + ); + }); + } } diff --git a/src/main/java/dongguk/osori/domain/user/entity/User.java b/src/main/java/dongguk/osori/domain/user/entity/User.java index ac35c11..378e399 100644 --- a/src/main/java/dongguk/osori/domain/user/entity/User.java +++ b/src/main/java/dongguk/osori/domain/user/entity/User.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @AllArgsConstructor @NoArgsConstructor @@ -37,9 +38,8 @@ public class User { @Column(nullable = true) private String introduce; - private Integer balance; - + private Integer balance; public void updateNickName(String nickname) { this.nickname = nickname; @@ -66,5 +66,10 @@ public void updateBalance(int amount) { @OneToMany(mappedBy = "following", cascade = CascadeType.ALL, orphanRemoval = true) private List followers = new ArrayList<>(); - + // 팔로우하는 사용자 목록 반환 + public List getFollowingUsers() { + return following.stream() + .map(Follow::getFollowing) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/dongguk/osori/domain/user/service/UserService.java b/src/main/java/dongguk/osori/domain/user/service/UserService.java index 03858c4..634ca4d 100644 --- a/src/main/java/dongguk/osori/domain/user/service/UserService.java +++ b/src/main/java/dongguk/osori/domain/user/service/UserService.java @@ -122,5 +122,16 @@ public User findUserByEmail(String email) { return userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); } + + // 팔로우하는 사용자 목록 반환 메소드 + @Transactional(readOnly = true) + public List getFollowingUsers(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저 아이디를 찾을 수 없음: " + userId)); + + return user.getFollowingUsers().stream() + .map(followedUser -> new UserDto(followedUser.getUserId(), followedUser.getNickname(), followedUser.getEmail())) + .collect(Collectors.toList()); + } }