diff --git a/src/main/java/com/example/FixLog/controller/FollowController.java b/src/main/java/com/example/FixLog/controller/FollowController.java index 84a23c6..7f211e1 100644 --- a/src/main/java/com/example/FixLog/controller/FollowController.java +++ b/src/main/java/com/example/FixLog/controller/FollowController.java @@ -9,6 +9,8 @@ import com.example.FixLog.service.FollowService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -23,8 +25,9 @@ public class FollowController { @PostMapping public ResponseEntity> follow( @RequestBody FollowRequestDto followRequestDto, - @RequestParam String requesterEmail // jwt 구현 전까지 임시 사용 -> 이후 AuthenticationPrincipal 사용 예정 + @AuthenticationPrincipal UserDetails userDetails // jwt 구현 전까지 임시 사용 -> 이후 AuthenticationPrincipal 사용 예정 ){ + String requesterEmail = userDetails.getUsername(); FollowResponseDto result = followService.follow(requesterEmail, followRequestDto.getTargetMemberId()); return ResponseEntity.ok(Response.success("팔로우 완료", result)); } @@ -33,8 +36,9 @@ public ResponseEntity> follow( @PostMapping("/unfollow") public ResponseEntity> unfollow( @RequestBody UnfollowRequestDto requestDto, - @RequestParam String requesterEmail) { - + @AuthenticationPrincipal UserDetails userDetails + ) { + String requesterEmail = userDetails.getUsername(); followService.unfollow(requesterEmail, requestDto.getTargetMemberId()); return ResponseEntity.ok(Response.success("언팔로우 완료", null)); } @@ -42,8 +46,9 @@ public ResponseEntity> unfollow( // 나를 팔로우하는 목록 조회 @GetMapping("/followers") public ResponseEntity>> getMyFollowers( - @RequestParam String requesterEmail) { - + @AuthenticationPrincipal UserDetails userDetails + ) { + String requesterEmail = userDetails.getUsername(); List followers = followService.getMyFollowers(requesterEmail); return ResponseEntity.ok(Response.success("나를 팔로우하는 목록 조회 성공", followers)); } @@ -51,8 +56,9 @@ public ResponseEntity>> getMyFollowers( // 내가 팔로우하는 목록 조회 @GetMapping("/followings") public ResponseEntity>> getMyFollowings( - @RequestParam String requesterEmail) { - + @AuthenticationPrincipal UserDetails userDetails + ) { + String requesterEmail = userDetails.getUsername(); List followings = followService.getMyFollowings(requesterEmail); return ResponseEntity.ok(Response.success("내가 팔로우 중인 목록 조회 성공", followings)); } diff --git a/src/main/java/com/example/FixLog/controller/MypagePostController.java b/src/main/java/com/example/FixLog/controller/MypagePostController.java new file mode 100644 index 0000000..c928095 --- /dev/null +++ b/src/main/java/com/example/FixLog/controller/MypagePostController.java @@ -0,0 +1,36 @@ +package com.example.FixLog.controller; + +import com.example.FixLog.dto.PageResponseDto; +import com.example.FixLog.dto.Response; +import com.example.FixLog.dto.post.MyPostPageResponseDto; +import com.example.FixLog.service.MypagePostService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mypage") +public class MypagePostController { + + private final MypagePostService mypagePostService; + + // 내가 쓴 글 보기 + @GetMapping("/posts") + public ResponseEntity>> getMyPosts( + @AuthenticationPrincipal UserDetails userDetails, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "4") int size, + @RequestParam(defaultValue = "0") int sort + ) { + String email = userDetails.getUsername(); + PageResponseDto data = mypagePostService.getMyPosts(email, page, sort, size); + return ResponseEntity.ok(Response.success("내가 작성한 글 보기 성공", data)); + } + +} diff --git a/src/main/java/com/example/FixLog/domain/post/Post.java b/src/main/java/com/example/FixLog/domain/post/Post.java index c5593b8..45ee175 100644 --- a/src/main/java/com/example/FixLog/domain/post/Post.java +++ b/src/main/java/com/example/FixLog/domain/post/Post.java @@ -4,9 +4,7 @@ import com.example.FixLog.domain.like.PostLike; import com.example.FixLog.domain.member.Member; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; import java.util.ArrayList; @@ -14,6 +12,8 @@ @Entity @Getter +@Builder +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Post { diff --git a/src/main/java/com/example/FixLog/dto/PageResponseDto.java b/src/main/java/com/example/FixLog/dto/PageResponseDto.java new file mode 100644 index 0000000..8cfc04e --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/PageResponseDto.java @@ -0,0 +1,35 @@ +package com.example.FixLog.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Getter; +import org.springframework.data.domain.Page; + +import java.util.List; +import java.util.function.Function; + +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PageResponseDto { + + private final List content; + private final int page; + private final int size; + private final long totalElements; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private final Integer totalPages; + + private PageResponseDto(Page page) { + this.content = page.getContent(); + this.page = page.getNumber() + 1; // 1부터 시작 + this.size = page.getSize(); + this.totalElements = page.getTotalElements(); + this.totalPages = page.getTotalPages() == 0 ? null : page.getTotalPages(); + } + + public static PageResponseDto from(Page page, Function mapper) { + Page mapped = page.map(mapper); + return new PageResponseDto<>(mapped); + } +} + diff --git a/src/main/java/com/example/FixLog/dto/post/MyPostPageResponseDto.java b/src/main/java/com/example/FixLog/dto/post/MyPostPageResponseDto.java new file mode 100644 index 0000000..6089085 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/post/MyPostPageResponseDto.java @@ -0,0 +1,43 @@ +package com.example.FixLog.dto.post; + +import com.example.FixLog.domain.post.Post; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MyPostPageResponseDto { + private Long postId; + private String postTitle; + private String postSummary; + private String imageUrl; + private List tags; + private LocalDateTime createdAt; + private int likeCount; + private int forkCount; + + public static MyPostPageResponseDto from(Post post, int forkCount) { + return MyPostPageResponseDto.builder() + .postId(post.getPostId()) + .postTitle(post.getPostTitle()) + .postSummary(generateSummary(post.getProblem())) + .imageUrl(post.getCoverImage()) + .tags(post.getPostTags().stream().map(tag -> tag.getTagId().getTagName()).toList()) + .createdAt(post.getCreatedAt()) + .likeCount(post.getPostLikes().size()) + .forkCount(forkCount) + .build(); + } + + private static String generateSummary(String content) { + if (content == null) return ""; + return content.length() > 200 ? content.substring(0, 200) + "..." : content; + } +} diff --git a/src/main/java/com/example/FixLog/mock/PostTestDataInitializer.java b/src/main/java/com/example/FixLog/mock/PostTestDataInitializer.java new file mode 100644 index 0000000..ccfc53f --- /dev/null +++ b/src/main/java/com/example/FixLog/mock/PostTestDataInitializer.java @@ -0,0 +1,85 @@ +package com.example.FixLog.mock; + +import com.example.FixLog.domain.bookmark.Bookmark; +import com.example.FixLog.domain.like.PostLike; +import com.example.FixLog.domain.post.Post; +import com.example.FixLog.domain.post.PostTag; +import com.example.FixLog.domain.tag.Tag; +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.repository.bookmark.BookmarkFolderRepository; +import com.example.FixLog.repository.bookmark.BookmarkRepository; +import com.example.FixLog.repository.like.PostLikeRepository; +import com.example.FixLog.repository.post.PostRepository; +import com.example.FixLog.repository.post.PostTagRepository; +import com.example.FixLog.repository.tag.TagRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; + +@Component +@Order(4) // Member, Tag, BookmarkFolder 이후에 실행 +@RequiredArgsConstructor +public class PostTestDataInitializer implements CommandLineRunner { + + private final MemberRepository memberRepository; + private final PostRepository postRepository; + private final TagRepository tagRepository; + private final PostTagRepository postTagRepository; + private final PostLikeRepository postLikeRepository; + private final BookmarkRepository bookmarkRepository; + private final BookmarkFolderRepository bookmarkFolderRepository; + + @Override + public void run(String... args) { + if (postRepository.count() == 0) { + memberRepository.findByEmail("test1@example.com").ifPresentOrElse(member -> { + + // 1. 게시글 생성 + Post post = Post.builder() + .userId(member) + .postTitle("개발을 하다 보면 많은 에러를 만난다") + .coverImage("https://cdn.example.com/images/test1.jpg") + .problem("만나고 싶지 않다") + .errorMessage("에러메세지 ~~ ") + .environment("스프링부트") + .reproduceCode("여긴 뭘까요") + .solutionCode("해결 !!") + .causeAnalysis("이유를 모름") + .referenceLink("no_error@@.com") + .extraContent("추가 설명입니다.") + .createdAt(LocalDateTime.now()) + .editedAt(LocalDateTime.now()) + .build(); + postRepository.save(post); + + // 2. 태그 연결 + List tags = tagRepository.findAll(); + if (!tags.isEmpty()) { + List postTags = tags.subList(0, Math.min(2, tags.size())).stream() + .map(tag -> new PostTag(post, tag)) + .toList(); + postTagRepository.saveAll(postTags); + } + + // 3. 좋아요 추가 + PostLike postLike = new PostLike(member, post); + postLikeRepository.save(postLike); + + // 4. 북마크 추가 (기본 폴더 사용) + bookmarkFolderRepository.findFirstByUserId(member).ifPresent(folder -> { + Bookmark bookmark = new Bookmark(folder, post); + bookmarkRepository.save(bookmark); + }); + + System.out.println("테스트용 게시글 1개, 태그/좋아요/북마크까지 생성 완료"); + + }, () -> { + System.out.println("test1@example.com 사용자가 없어 게시글 생성 생략됨"); + }); + } + } +} diff --git a/src/main/java/com/example/FixLog/mock/TagTestDataInitializer.java b/src/main/java/com/example/FixLog/mock/TagTestDataInitializer.java index 0658aaf..ef3b59f 100644 --- a/src/main/java/com/example/FixLog/mock/TagTestDataInitializer.java +++ b/src/main/java/com/example/FixLog/mock/TagTestDataInitializer.java @@ -4,6 +4,7 @@ import com.example.FixLog.repository.tag.TagRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.List; @@ -12,6 +13,7 @@ @Component @RequiredArgsConstructor +@Order(3) public class TagTestDataInitializer implements CommandLineRunner { private final TagRepository tagRepository; diff --git a/src/main/java/com/example/FixLog/repository/bookmark/BookmarkFolderRepository.java b/src/main/java/com/example/FixLog/repository/bookmark/BookmarkFolderRepository.java index 85c5ea7..430568d 100644 --- a/src/main/java/com/example/FixLog/repository/bookmark/BookmarkFolderRepository.java +++ b/src/main/java/com/example/FixLog/repository/bookmark/BookmarkFolderRepository.java @@ -6,8 +6,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; +import java.util.Optional; + public interface BookmarkFolderRepository extends JpaRepository { Page findAllByUserId(Member userId, Pageable pageable); BookmarkFolder findByUserId(Member userId); + Optional findFirstByUserId(Member userId); // 첫 번째 폴더만 가져올 때 Optional } diff --git a/src/main/java/com/example/FixLog/repository/fork/ForkRepository.java b/src/main/java/com/example/FixLog/repository/fork/ForkRepository.java new file mode 100644 index 0000000..7cb6a77 --- /dev/null +++ b/src/main/java/com/example/FixLog/repository/fork/ForkRepository.java @@ -0,0 +1,19 @@ +package com.example.FixLog.repository.fork; + +import com.example.FixLog.domain.fork.Fork; +import com.example.FixLog.domain.post.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ForkRepository extends JpaRepository { + @Query(""" + SELECT f.originalPostId.postId, COUNT(f) + FROM Fork f + WHERE f.originalPostId IN :posts + GROUP BY f.originalPostId.postId + """) + List countForksByOriginalPosts(@Param("posts") List posts); // 원본글 기반 포크 수 +} diff --git a/src/main/java/com/example/FixLog/repository/post/PostRepository.java b/src/main/java/com/example/FixLog/repository/post/PostRepository.java index 0f9e2c6..e8c7c33 100644 --- a/src/main/java/com/example/FixLog/repository/post/PostRepository.java +++ b/src/main/java/com/example/FixLog/repository/post/PostRepository.java @@ -1,8 +1,10 @@ package com.example.FixLog.repository.post; +import com.example.FixLog.domain.member.Member; import com.example.FixLog.domain.post.Post; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -12,4 +14,7 @@ public interface PostRepository extends JpaRepository { List findTop12ByOrderByPostLikesDesc(); Page findAllByOrderByCreatedAtDesc(Pageable pageable); Page findAllByOrderByPostLikesDesc(Pageable pageable); + + @EntityGraph(attributePaths = {"postLikes"}) + Page findByUserId(Member userId, Pageable pageable); } diff --git a/src/main/java/com/example/FixLog/service/MypagePostService.java b/src/main/java/com/example/FixLog/service/MypagePostService.java new file mode 100644 index 0000000..7e46619 --- /dev/null +++ b/src/main/java/com/example/FixLog/service/MypagePostService.java @@ -0,0 +1,60 @@ +package com.example.FixLog.service; + +import com.example.FixLog.domain.member.Member; +import com.example.FixLog.domain.post.Post; +import com.example.FixLog.dto.PageResponseDto; +import com.example.FixLog.dto.post.MyPostPageResponseDto; +import com.example.FixLog.exception.CustomException; +import com.example.FixLog.exception.ErrorCode; +import com.example.FixLog.repository.MemberRepository; +import com.example.FixLog.repository.fork.ForkRepository; +import com.example.FixLog.repository.post.PostRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class MypagePostService { + + private final PostRepository postRepository; + private final MemberRepository memberRepository; + private final ForkRepository forkRepository; + + public MypagePostService(PostRepository postRepository, MemberRepository memberRepository, ForkRepository forkRepository) { + this.postRepository = postRepository; + this.memberRepository = memberRepository; + this.forkRepository = forkRepository; + } + + // 내가 쓴 글 보기 + public PageResponseDto getMyPosts(String email, int page, int sort, int size) { + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); + + // 1: 오래된순, 0: 최신순 + Sort.Direction direction = (sort == 1) ? Sort.Direction.ASC : Sort.Direction.DESC; + Pageable pageable = PageRequest.of(page, size, Sort.by(direction, "createdAt")); + + Page postPage = postRepository.findByUserId(member, pageable); + List posts = postPage.getContent(); + + // fork count 한번에 조회 + List forkCounts = forkRepository.countForksByOriginalPosts(posts); + Map forkCountMap = forkCounts.stream() + .collect(Collectors.toMap( + row -> (Long) row[0], // postId + row -> ((Long) row[1]).intValue() // 포크 카운트 (int) + )); + + return PageResponseDto.from(postPage, post -> + MyPostPageResponseDto.from(post, forkCountMap.getOrDefault(post.getPostId(), 0) // 없으면 0 반환 + ) + ); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8052a7f..c813596 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,4 +1,4 @@ -spring.application.name=fixlog +spring.application.name=FixLog # DB setting spring.h2.console.enabled=true @@ -11,7 +11,7 @@ spring.datasource.username=sa spring.datasource.password= spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect # JWT