diff --git a/src/main/java/com/blog/domain/board/application/dto/PostReadAllResponse.java b/src/main/java/com/blog/domain/board/application/dto/PostReadAllResponse.java index f33b8c8..9f2cdc0 100644 --- a/src/main/java/com/blog/domain/board/application/dto/PostReadAllResponse.java +++ b/src/main/java/com/blog/domain/board/application/dto/PostReadAllResponse.java @@ -6,13 +6,14 @@ @Builder public record PostReadAllResponse( - List post, - long pageMax + List posts, + long pageMax ) { - public static PostReadAllResponse toResponse(List postDtos, int pageSize) { + public static PostReadAllResponse toResponse(List postDtos, int pageSize) { return PostReadAllResponse.builder() - .post(postDtos) - .pageMax(pageSize) - .build(); + .posts(postDtos) + .pageMax(pageSize) + .build(); } } + diff --git a/src/main/java/com/blog/domain/board/application/dto/PostReadResponse.java b/src/main/java/com/blog/domain/board/application/dto/PostReadResponse.java index e93a9ba..aeb4347 100644 --- a/src/main/java/com/blog/domain/board/application/dto/PostReadResponse.java +++ b/src/main/java/com/blog/domain/board/application/dto/PostReadResponse.java @@ -14,41 +14,46 @@ @Builder public record PostReadResponse( - @Schema(description = "게시글 id", example = "3fa85f64-5717-4562-b3fc-2c963f66afa6") - UUID postId, - @Schema(description = "게시글 제목", example = "게시글 제목") - String title, - @ArraySchema(arraySchema = @Schema(implementation = ContentDto.class)) - List contents, - @Schema(description = "게시글 소유 여부", example = "true") - Boolean isOwner, - @ArraySchema(arraySchema = @Schema(implementation = CommentGetDto.class)) - List comments, - @Schema(description = "작성자 닉네임", example = "멋쟁이프론트") - String nickName, - @Schema(description = "작성자 프로필 사진 url", example = "url") - String profileUrl, - @Schema(description = "게시글 작성일자", implementation = LocalDateTime.class) - LocalDateTime createdAt + @Schema(description = "게시글 id", example = "3fa85f64-5717-4562-b3fc-2c963f66afa6") + UUID postId, + @Schema(description = "게시글 제목", example = "게시글 제목") + String title, + @ArraySchema(arraySchema = @Schema(implementation = ContentDto.class)) + List contents, + @Schema(description = "게시글 소유 여부", example = "true") + Boolean isOwner, + @ArraySchema(arraySchema = @Schema(implementation = CommentGetDto.class)) + List comments, + @Schema(description = "작성자 닉네임", example = "멋쟁이프론트") + String nickName, + @Schema(description = "작성자 프로필 사진 url", example = "url") + String profileUrl, + @Schema(description = "작성자 한 줄 소개", example = "안녕하세요. 반갑습니다.") + String introduction, + @Schema(description = "게시글 작성일자", implementation = LocalDateTime.class) + LocalDateTime createdAt ) { - public static PostReadResponse toResponse(Post post, User user, List contents, List comments) { - List dtos = comments.stream() - .map(comment -> CommentGetDto.toResponse(comment, user)) - .toList(); + public static PostReadResponse toResponse(Post post, User user, List contents, List comments) { + List dtos = comments.stream() + .map(comment -> CommentGetDto.toResponse(comment, user)) + .toList(); - List contentDtos = contents.stream() - .map(ContentDto::fromContent) - .toList(); + List contentDtos = contents.stream() + .map(ContentDto::fromContent) + .toList(); - return PostReadResponse.builder() - .postId(post.getId()) - .title(post.getTitle()) - .contents(contentDtos) - .isOwner(user != null && post.getUser().equals(user)) - .nickName(post.getUser().getNickname()) - .profileUrl(post.getUser().getProfilePicture()) - .createdAt(post.getCreatedAt()) - .comments(dtos) - .build(); - } + User postAuthor = post.getUser(); + return PostReadResponse.builder() + .postId(post.getId()) + .title(post.getTitle()) + .contents(contentDtos) + .isOwner(user != null && postAuthor.getId().equals(user.getId())) + .nickName(postAuthor.getNickname()) + .profileUrl(postAuthor.getProfilePicture()) + .introduction(postAuthor.getIntroduction()) + .createdAt(post.getCreatedAt()) + .comments(dtos) + .build(); + } } + diff --git a/src/main/java/com/blog/domain/board/application/dto/PostSummaryResponse.java b/src/main/java/com/blog/domain/board/application/dto/PostSummaryResponse.java new file mode 100644 index 0000000..4e95204 --- /dev/null +++ b/src/main/java/com/blog/domain/board/application/dto/PostSummaryResponse.java @@ -0,0 +1,33 @@ +package com.blog.domain.board.application.dto; + +import com.blog.domain.board.domain.entity.Post; +import java.time.LocalDateTime; +import java.util.UUID; +import lombok.Builder; + +@Builder +public record PostSummaryResponse( + UUID postId, + String title, + String nickName, + String profileUrl, + LocalDateTime createdAt, + long commentCount +) { + public static PostSummaryResponse from(Post post) { + // N+1 문제를 방지하기 위해 post.getComments().size() 대신 + // commentRepository.countByPost(post) 등을 사용하는 것을 고려해볼 수 있습니다. + // 하지만 현재 구조에서는 size()를 사용하겠습니다. + long count = post.getComments() == null ? 0 : post.getComments().size(); + + return PostSummaryResponse.builder() + .postId(post.getId()) + .title(post.getTitle()) + .nickName(post.getUser().getNickname()) + .profileUrl(post.getUser().getProfilePicture()) + .createdAt(post.getCreatedAt()) + .commentCount(count) + .build(); + } +} + diff --git a/src/main/java/com/blog/domain/board/application/usecase/PostManageUsecase.java b/src/main/java/com/blog/domain/board/application/usecase/PostManageUsecase.java index 3452e98..0f7c747 100644 --- a/src/main/java/com/blog/domain/board/application/usecase/PostManageUsecase.java +++ b/src/main/java/com/blog/domain/board/application/usecase/PostManageUsecase.java @@ -1,15 +1,9 @@ package com.blog.domain.board.application.usecase; -import java.util.List; -import java.util.UUID; - -import org.springframework.data.domain.Page; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import com.blog.domain.board.application.dto.PostCreateRequest; import com.blog.domain.board.application.dto.PostReadAllResponse; import com.blog.domain.board.application.dto.PostReadResponse; +import com.blog.domain.board.application.dto.PostSummaryResponse; import com.blog.domain.board.application.dto.PostUpdateRequest; import com.blog.domain.board.domain.entity.Content; import com.blog.domain.board.domain.entity.Post; @@ -26,8 +20,12 @@ import com.blog.domain.comment.domain.service.CommentGetService; import com.blog.domain.user.domain.entity.User; import com.blog.domain.user.domain.service.UserGetService; - +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -75,16 +73,12 @@ public PostReadResponse readPostNoToken(UUID postId) { @Transactional(readOnly = true) public PostReadAllResponse readAllPost(Long userId, int size, int page) { - User user = userGetService.find(userId); Page postPage = postGetService.findAll(size, page); List posts = postPage.getContent(); - List dtos = posts.stream() - .map(post -> { - List contents = contentGetService.findAll(post); - List comments = commentGetService.findALlByPost(post); - return PostReadResponse.toResponse(post, user, contents, comments); - }).toList(); + List dtos = posts.stream() + .map(PostSummaryResponse::from) + .toList(); return PostReadAllResponse.toResponse(dtos, postPage.getTotalPages()); } @@ -94,12 +88,9 @@ public PostReadAllResponse readAllPostNoToken(int size, int page) { Page postPage = postGetService.findAll(size, page); List posts = postPage.getContent(); - List dtos = posts.stream() - .map(post -> { - List contents = contentGetService.findAll(post); - List comments = commentGetService.findALlByPost(post); - return PostReadResponse.toResponse(post, null, contents, comments); - }).toList(); + List dtos = posts.stream() + .map(PostSummaryResponse::from) + .toList(); return PostReadAllResponse.toResponse(dtos, postPage.getTotalPages()); } @@ -129,3 +120,4 @@ public void deletePost(Long userId, UUID postId) { postDeleteService.delete(post); } } + diff --git a/src/main/java/com/blog/domain/board/domain/entity/Post.java b/src/main/java/com/blog/domain/board/domain/entity/Post.java index d36d4ca..835b180 100644 --- a/src/main/java/com/blog/domain/board/domain/entity/Post.java +++ b/src/main/java/com/blog/domain/board/domain/entity/Post.java @@ -1,6 +1,8 @@ package com.blog.domain.board.domain.entity; +import com.blog.domain.comment.domain.entity.Comment; import com.blog.domain.user.domain.entity.User; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -9,9 +11,12 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; @@ -43,15 +48,24 @@ public class Post { @CreationTimestamp private LocalDateTime createdAt; + @Builder.Default + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); + + @Builder.Default + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List contents = new ArrayList<>(); + public static Post CreatePost(String title, User user) { return Post.builder(). - title(title). - user(user). - build(); + title(title). + user(user). + build(); } public void update(String title) { this.title = title; } } + diff --git a/src/main/java/com/blog/domain/comment/application/dto/CommentGetDto.java b/src/main/java/com/blog/domain/comment/application/dto/CommentGetDto.java index bcc9370..af470fb 100644 --- a/src/main/java/com/blog/domain/comment/application/dto/CommentGetDto.java +++ b/src/main/java/com/blog/domain/comment/application/dto/CommentGetDto.java @@ -3,26 +3,35 @@ import com.blog.domain.comment.domain.entity.Comment; import com.blog.domain.user.domain.entity.User; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; import lombok.Builder; @Schema(description = "댓글 DTO") @Builder public record CommentGetDto( - @Schema(description = "댓글 Id", example = "1") - long commentId, - @Schema(description = "댓글 내용", example = "댓글 내용") - String content, - @Schema(description = "작성자 닉네임", example = "작성자 닉네임") - String nickName, - @Schema(description = "댓글 소유 여부", example = "true") - boolean isOwner + @Schema(description = "댓글 Id", example = "1") + long commentId, + @Schema(description = "댓글 내용", example = "댓글 내용") + String content, + @Schema(description = "작성자 닉네임", example = "작성자 닉네임") + String nickName, + @Schema(description = "작성자 프로필 이미지 URL", example = "https://example.com/profile.jpg") + String profileUrl, + @Schema(description = "댓글 작성 시간") + LocalDateTime createdAt, + @Schema(description = "댓글 소유 여부", example = "true") + boolean isOwner ) { - public static CommentGetDto toResponse(Comment comment, User user) { - return CommentGetDto.builder() - .commentId(comment.getId()) - .content(comment.getContent()) - .nickName(user == null ? null : user.getNickname()) - .isOwner(comment.getUser().equals(user)) - .build(); - } + public static CommentGetDto toResponse(Comment comment, User loginUser) { + User commentAuthor = comment.getUser(); + return CommentGetDto.builder() + .commentId(comment.getId()) + .content(comment.getContent()) + .nickName(commentAuthor.getNickname()) + .profileUrl(commentAuthor.getProfilePicture()) + .createdAt(comment.getCreatedAt()) + .isOwner(loginUser != null && commentAuthor.getId().equals(loginUser.getId())) + .build(); + } } + diff --git a/src/main/java/com/blog/domain/comment/domain/entity/Comment.java b/src/main/java/com/blog/domain/comment/domain/entity/Comment.java index a054b30..03b022c 100644 --- a/src/main/java/com/blog/domain/comment/domain/entity/Comment.java +++ b/src/main/java/com/blog/domain/comment/domain/entity/Comment.java @@ -12,10 +12,12 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; @Entity @Builder @@ -41,12 +43,16 @@ public class Comment { @NotBlank private String content; + @CreationTimestamp + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + public static Comment of(String content, Post post, User user) { return Comment.builder() - .content(content) - .post(post) - .user(user) - .build(); + .content(content) + .post(post) + .user(user) + .build(); } public void updateContent(String content) { diff --git a/src/main/java/com/blog/domain/user/application/dto/response/UserGetResponse.java b/src/main/java/com/blog/domain/user/application/dto/response/UserGetResponse.java index 236b380..8ec5b3e 100644 --- a/src/main/java/com/blog/domain/user/application/dto/response/UserGetResponse.java +++ b/src/main/java/com/blog/domain/user/application/dto/response/UserGetResponse.java @@ -6,7 +6,10 @@ public record UserGetResponse( Long id, String email, String nickname, - String profilePicture + String profilePicture, + String name, + String birthDate, + String introduction ) { public static UserGetResponse from(User user) { @@ -14,7 +17,10 @@ public static UserGetResponse from(User user) { user.getId(), user.getEmail(), user.getNickname(), - user.getProfilePicture() + user.getProfilePicture(), + user.getName(), + user.getBirthDate().toString(), + user.getIntroduction() ); } } diff --git a/src/main/java/com/blog/domain/user/domain/service/UserService.java b/src/main/java/com/blog/domain/user/domain/service/UserService.java index d6f427e..024f275 100644 --- a/src/main/java/com/blog/domain/user/domain/service/UserService.java +++ b/src/main/java/com/blog/domain/user/domain/service/UserService.java @@ -9,6 +9,7 @@ import com.blog.domain.user.exception.UserNotFoundException; import com.blog.global.common.auth.MemberContext; import com.blog.global.common.auth.TokenMemberInfo; +import com.blog.global.common.exception.BadRequestException; import jakarta.transaction.Transactional; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -96,6 +97,10 @@ public void updatePassword(String password) { TokenMemberInfo member = MemberContext.getMember(); User user = this.findById(member.id()); + if (user.getKakaoId() != null) { + throw new BadRequestException("소셜 로그인 유저는 비밀번호를 변경할 수 없습니다."); + } + user.setPassword(this.hashPassword(password)); } @@ -127,3 +132,4 @@ public void updateUser(UserPatchRequest userPatchRequest) { this.save(user); } } + diff --git a/src/main/java/com/blog/global/common/exception/BadRequestException.java b/src/main/java/com/blog/global/common/exception/BadRequestException.java new file mode 100644 index 0000000..3298a12 --- /dev/null +++ b/src/main/java/com/blog/global/common/exception/BadRequestException.java @@ -0,0 +1,11 @@ +package com.blog.global.common.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { + super(message); + } +}