From b41835d9c8d6dcf2f590e11c3ce908f9b451b8eb Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Mon, 4 Aug 2025 20:37:20 +0800 Subject: [PATCH] refactor: extract post dtos --- .../openisle/controller/PostController.java | 237 ++---------------- .../main/java/com/openisle/dto/AuthorDto.java | 14 ++ .../java/com/openisle/dto/CategoryDto.java | 16 ++ .../java/com/openisle/dto/CommentDto.java | 21 ++ .../java/com/openisle/dto/PostDetailDto.java | 16 ++ .../java/com/openisle/dto/PostRequest.java | 18 ++ .../java/com/openisle/dto/PostSummaryDto.java | 30 +++ .../java/com/openisle/dto/ReactionDto.java | 18 ++ .../main/java/com/openisle/dto/TagDto.java | 16 ++ .../java/com/openisle/mapper/PostMapper.java | 140 +++++++++++ 10 files changed, 311 insertions(+), 215 deletions(-) create mode 100644 backend/src/main/java/com/openisle/dto/AuthorDto.java create mode 100644 backend/src/main/java/com/openisle/dto/CategoryDto.java create mode 100644 backend/src/main/java/com/openisle/dto/CommentDto.java create mode 100644 backend/src/main/java/com/openisle/dto/PostDetailDto.java create mode 100644 backend/src/main/java/com/openisle/dto/PostRequest.java create mode 100644 backend/src/main/java/com/openisle/dto/PostSummaryDto.java create mode 100644 backend/src/main/java/com/openisle/dto/ReactionDto.java create mode 100644 backend/src/main/java/com/openisle/dto/TagDto.java create mode 100644 backend/src/main/java/com/openisle/mapper/PostMapper.java diff --git a/backend/src/main/java/com/openisle/controller/PostController.java b/backend/src/main/java/com/openisle/controller/PostController.java index 9b49ddc5c..8c1c16024 100644 --- a/backend/src/main/java/com/openisle/controller/PostController.java +++ b/backend/src/main/java/com/openisle/controller/PostController.java @@ -1,25 +1,22 @@ package com.openisle.controller; -import com.openisle.model.Comment; +import com.openisle.dto.PostDetailDto; +import com.openisle.dto.PostRequest; +import com.openisle.dto.PostSummaryDto; +import com.openisle.mapper.PostMapper; import com.openisle.model.Post; -import com.openisle.model.Reaction; -import com.openisle.service.CommentService; -import com.openisle.model.CommentSort; -import com.openisle.service.PostService; -import com.openisle.service.ReactionService; import com.openisle.service.CaptchaService; import com.openisle.service.DraftService; +import com.openisle.service.LevelService; +import com.openisle.service.PostService; import com.openisle.service.SubscriptionService; import com.openisle.service.UserVisitService; -import com.openisle.service.LevelService; -import lombok.Data; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; -import org.springframework.beans.factory.annotation.Value; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -28,13 +25,12 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class PostController { private final PostService postService; - private final CommentService commentService; - private final ReactionService reactionService; private final SubscriptionService subscriptionService; private final LevelService levelService; private final CaptchaService captchaService; private final DraftService draftService; private final UserVisitService userVisitService; + private final PostMapper postMapper; @Value("${app.captcha.enabled:false}") private boolean captchaEnabled; @@ -43,24 +39,24 @@ public class PostController { private boolean postCaptchaEnabled; @PostMapping - public ResponseEntity createPost(@RequestBody PostRequest req, Authentication auth) { + public ResponseEntity createPost(@RequestBody PostRequest req, Authentication auth) { if (captchaEnabled && postCaptchaEnabled && !captchaService.verify(req.getCaptcha())) { return ResponseEntity.badRequest().build(); } Post post = postService.createPost(auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent(), req.getTagIds()); draftService.deleteDraft(auth.getName()); - PostDto dto = toDto(post); + PostDetailDto dto = postMapper.toDetailDto(post, auth.getName()); dto.setReward(levelService.awardForPost(auth.getName())); return ResponseEntity.ok(dto); } @PutMapping("/{id}") - public ResponseEntity updatePost(@PathVariable Long id, @RequestBody PostRequest req, + public ResponseEntity updatePost(@PathVariable Long id, @RequestBody PostRequest req, Authentication auth) { Post post = postService.updatePost(id, auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent(), req.getTagIds()); - return ResponseEntity.ok(toDto(post)); + return ResponseEntity.ok(postMapper.toDetailDto(post, auth.getName())); } @DeleteMapping("/{id}") @@ -69,14 +65,14 @@ public class PostController { } @GetMapping("/{id}") - public ResponseEntity getPost(@PathVariable Long id, Authentication auth) { + public ResponseEntity getPost(@PathVariable Long id, Authentication auth) { String viewer = auth != null ? auth.getName() : null; Post post = postService.viewPost(id, viewer); - return ResponseEntity.ok(toDto(post, viewer)); + return ResponseEntity.ok(postMapper.toDetailDto(post, viewer)); } @GetMapping - public List listPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, + public List listPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, @RequestParam(value = "categoryIds", required = false) List categoryIds, @RequestParam(value = "tagId", required = false) Long tagId, @RequestParam(value = "tagIds", required = false) List tagIds, @@ -101,19 +97,19 @@ public class PostController { if (hasCategories && hasTags) { return postService.listPostsByCategoriesAndTags(ids, tids, page, pageSize) - .stream().map(this::toDto).collect(Collectors.toList()); + .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); } if (hasTags) { return postService.listPostsByTags(tids, page, pageSize) - .stream().map(this::toDto).collect(Collectors.toList()); + .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); } return postService.listPostsByCategories(ids, page, pageSize) - .stream().map(this::toDto).collect(Collectors.toList()); + .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); } @GetMapping("/ranking") - public List rankingPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, + public List rankingPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, @RequestParam(value = "categoryIds", required = false) List categoryIds, @RequestParam(value = "tagId", required = false) Long tagId, @RequestParam(value = "tagIds", required = false) List tagIds, @@ -134,11 +130,11 @@ public class PostController { } return postService.listPostsByViews(ids, tids, page, pageSize) - .stream().map(this::toDto).collect(Collectors.toList()); + .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); } @GetMapping("/latest-reply") - public List latestReplyPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, + public List latestReplyPosts(@RequestParam(value = "categoryId", required = false) Long categoryId, @RequestParam(value = "categoryIds", required = false) List categoryIds, @RequestParam(value = "tagId", required = false) Long tagId, @RequestParam(value = "tagIds", required = false) List tagIds, @@ -159,195 +155,6 @@ public class PostController { } return postService.listPostsByLatestReply(ids, tids, page, pageSize) - .stream().map(this::toDto).collect(Collectors.toList()); - } - - private PostDto toDto(Post post) { - PostDto dto = new PostDto(); - dto.setId(post.getId()); - dto.setTitle(post.getTitle()); - dto.setContent(post.getContent()); - dto.setCreatedAt(post.getCreatedAt()); - dto.setAuthor(toAuthorDto(post.getAuthor())); - dto.setCategory(toCategoryDto(post.getCategory())); - dto.setTags(post.getTags().stream().map(this::toTagDto).collect(Collectors.toList())); - dto.setViews(post.getViews()); - dto.setStatus(post.getStatus()); - dto.setPinnedAt(post.getPinnedAt()); - - List reactions = reactionService.getReactionsForPost(post.getId()) - .stream() - .map(this::toReactionDto) - .collect(Collectors.toList()); - dto.setReactions(reactions); - - List comments = commentService.getCommentsForPost(post.getId(), CommentSort.OLDEST) - .stream() - .map(this::toCommentDtoWithReplies) - .collect(Collectors.toList()); - dto.setComments(comments); - - java.util.List participants = commentService.getParticipants(post.getId(), 5); - dto.setParticipants(participants.stream().map(this::toAuthorDto).collect(Collectors.toList())); - - java.time.LocalDateTime last = commentService.getLastCommentTime(post.getId()); - dto.setLastReplyAt(last != null ? last : post.getCreatedAt()); - dto.setReward(0); - - return dto; - } - - private PostDto toDto(Post post, String viewer) { - PostDto dto = toDto(post); - if (viewer != null) { - dto.setSubscribed(subscriptionService.isPostSubscribed(viewer, post.getId())); - } else { - dto.setSubscribed(false); - } - return dto; - } - - private CommentDto toCommentDtoWithReplies(Comment comment) { - CommentDto dto = toCommentDto(comment); - List replies = commentService.getReplies(comment.getId()).stream() - .map(this::toCommentDtoWithReplies) - .collect(Collectors.toList()); - dto.setReplies(replies); - - List reactions = reactionService.getReactionsForComment(comment.getId()) - .stream() - .map(this::toReactionDto) - .collect(Collectors.toList()); - dto.setReactions(reactions); - - return dto; - } - - private CommentDto toCommentDto(Comment comment) { - CommentDto dto = new CommentDto(); - dto.setId(comment.getId()); - dto.setContent(comment.getContent()); - dto.setCreatedAt(comment.getCreatedAt()); - dto.setAuthor(toAuthorDto(comment.getAuthor())); - dto.setReward(0); - return dto; - } - - private ReactionDto toReactionDto(Reaction reaction) { - ReactionDto dto = new ReactionDto(); - dto.setId(reaction.getId()); - dto.setType(reaction.getType()); - dto.setUser(reaction.getUser().getUsername()); - if (reaction.getPost() != null) { - dto.setPostId(reaction.getPost().getId()); - } - if (reaction.getComment() != null) { - dto.setCommentId(reaction.getComment().getId()); - } - dto.setReward(0); - return dto; - } - - private CategoryDto toCategoryDto(com.openisle.model.Category category) { - CategoryDto dto = new CategoryDto(); - dto.setId(category.getId()); - dto.setName(category.getName()); - dto.setDescription(category.getDescription()); - dto.setIcon(category.getIcon()); - dto.setSmallIcon(category.getSmallIcon()); - return dto; - } - - private TagDto toTagDto(com.openisle.model.Tag tag) { - TagDto dto = new TagDto(); - dto.setId(tag.getId()); - dto.setName(tag.getName()); - dto.setDescription(tag.getDescription()); - dto.setIcon(tag.getIcon()); - dto.setSmallIcon(tag.getSmallIcon()); - return dto; - } - - private AuthorDto toAuthorDto(com.openisle.model.User user) { - AuthorDto dto = new AuthorDto(); - dto.setId(user.getId()); - dto.setUsername(user.getUsername()); - dto.setAvatar(user.getAvatar()); - return dto; - } - - @Data - private static class PostRequest { - private Long categoryId; - private String title; - private String content; - private java.util.List tagIds; - private String captcha; - } - - @Data - private static class PostDto { - private Long id; - private String title; - private String content; - private LocalDateTime createdAt; - private AuthorDto author; - private CategoryDto category; - private java.util.List tags; - private long views; - private com.openisle.model.PostStatus status; - private LocalDateTime pinnedAt; - private LocalDateTime lastReplyAt; - private List comments; - private List reactions; - private java.util.List participants; - private boolean subscribed; - private int reward; - } - - @Data - private static class CategoryDto { - private Long id; - private String name; - private String description; - private String icon; - private String smallIcon; - } - - @Data - private static class TagDto { - private Long id; - private String name; - private String description; - private String icon; - private String smallIcon; - } - - @Data - private static class AuthorDto { - private Long id; - private String username; - private String avatar; - } - - @Data - private static class CommentDto { - private Long id; - private String content; - private LocalDateTime createdAt; - private AuthorDto author; - private List replies; - private List reactions; - private int reward; - } - - @Data - private static class ReactionDto { - private Long id; - private com.openisle.model.ReactionType type; - private String user; - private Long postId; - private Long commentId; - private int reward; + .stream().map(postMapper::toSummaryDto).collect(Collectors.toList()); } } diff --git a/backend/src/main/java/com/openisle/dto/AuthorDto.java b/backend/src/main/java/com/openisle/dto/AuthorDto.java new file mode 100644 index 000000000..9892df611 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/AuthorDto.java @@ -0,0 +1,14 @@ +package com.openisle.dto; + +import lombok.Data; + +/** + * DTO representing a post or comment author. + */ +@Data +public class AuthorDto { + private Long id; + private String username; + private String avatar; +} + diff --git a/backend/src/main/java/com/openisle/dto/CategoryDto.java b/backend/src/main/java/com/openisle/dto/CategoryDto.java new file mode 100644 index 000000000..d09651f44 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/CategoryDto.java @@ -0,0 +1,16 @@ +package com.openisle.dto; + +import lombok.Data; + +/** + * DTO representing a post category. + */ +@Data +public class CategoryDto { + private Long id; + private String name; + private String description; + private String icon; + private String smallIcon; +} + diff --git a/backend/src/main/java/com/openisle/dto/CommentDto.java b/backend/src/main/java/com/openisle/dto/CommentDto.java new file mode 100644 index 000000000..17babe624 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/CommentDto.java @@ -0,0 +1,21 @@ +package com.openisle.dto; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO representing a comment and its nested replies. + */ +@Data +public class CommentDto { + private Long id; + private String content; + private LocalDateTime createdAt; + private AuthorDto author; + private List replies; + private List reactions; + private int reward; +} + diff --git a/backend/src/main/java/com/openisle/dto/PostDetailDto.java b/backend/src/main/java/com/openisle/dto/PostDetailDto.java new file mode 100644 index 000000000..8cd8e402b --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/PostDetailDto.java @@ -0,0 +1,16 @@ +package com.openisle.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * Detailed DTO for a post, including comments. + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PostDetailDto extends PostSummaryDto { + private List comments; +} + diff --git a/backend/src/main/java/com/openisle/dto/PostRequest.java b/backend/src/main/java/com/openisle/dto/PostRequest.java new file mode 100644 index 000000000..0c40537e1 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/PostRequest.java @@ -0,0 +1,18 @@ +package com.openisle.dto; + +import lombok.Data; + +import java.util.List; + +/** + * Request body for creating or updating a post. + */ +@Data +public class PostRequest { + private Long categoryId; + private String title; + private String content; + private List tagIds; + private String captcha; +} + diff --git a/backend/src/main/java/com/openisle/dto/PostSummaryDto.java b/backend/src/main/java/com/openisle/dto/PostSummaryDto.java new file mode 100644 index 000000000..079dc8c2d --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/PostSummaryDto.java @@ -0,0 +1,30 @@ +package com.openisle.dto; + +import com.openisle.model.PostStatus; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * Lightweight DTO for listing posts without comments. + */ +@Data +public class PostSummaryDto { + private Long id; + private String title; + private String content; + private LocalDateTime createdAt; + private AuthorDto author; + private CategoryDto category; + private List tags; + private long views; + private PostStatus status; + private LocalDateTime pinnedAt; + private LocalDateTime lastReplyAt; + private List reactions; + private List participants; + private boolean subscribed; + private int reward; +} + diff --git a/backend/src/main/java/com/openisle/dto/ReactionDto.java b/backend/src/main/java/com/openisle/dto/ReactionDto.java new file mode 100644 index 000000000..b57a3d333 --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/ReactionDto.java @@ -0,0 +1,18 @@ +package com.openisle.dto; + +import com.openisle.model.ReactionType; +import lombok.Data; + +/** + * DTO representing a reaction on a post or comment. + */ +@Data +public class ReactionDto { + private Long id; + private ReactionType type; + private String user; + private Long postId; + private Long commentId; + private int reward; +} + diff --git a/backend/src/main/java/com/openisle/dto/TagDto.java b/backend/src/main/java/com/openisle/dto/TagDto.java new file mode 100644 index 000000000..85f94b92e --- /dev/null +++ b/backend/src/main/java/com/openisle/dto/TagDto.java @@ -0,0 +1,16 @@ +package com.openisle.dto; + +import lombok.Data; + +/** + * DTO representing a tag. + */ +@Data +public class TagDto { + private Long id; + private String name; + private String description; + private String icon; + private String smallIcon; +} + diff --git a/backend/src/main/java/com/openisle/mapper/PostMapper.java b/backend/src/main/java/com/openisle/mapper/PostMapper.java new file mode 100644 index 000000000..5feeac366 --- /dev/null +++ b/backend/src/main/java/com/openisle/mapper/PostMapper.java @@ -0,0 +1,140 @@ +package com.openisle.mapper; + +import com.openisle.dto.*; +import com.openisle.model.*; +import com.openisle.service.CommentService; +import com.openisle.service.ReactionService; +import com.openisle.service.SubscriptionService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Mapper responsible for converting domain models into DTOs. + */ +@Component +@RequiredArgsConstructor +public class PostMapper { + + private final CommentService commentService; + private final ReactionService reactionService; + private final SubscriptionService subscriptionService; + + public PostSummaryDto toSummaryDto(Post post) { + PostSummaryDto dto = new PostSummaryDto(); + applyCommon(post, dto); + return dto; + } + + public PostDetailDto toDetailDto(Post post, String viewer) { + PostDetailDto dto = new PostDetailDto(); + applyCommon(post, dto); + List comments = commentService.getCommentsForPost(post.getId(), CommentSort.OLDEST) + .stream() + .map(this::toCommentDtoWithReplies) + .collect(Collectors.toList()); + dto.setComments(comments); + dto.setSubscribed(viewer != null && subscriptionService.isPostSubscribed(viewer, post.getId())); + return dto; + } + + private void applyCommon(Post post, PostSummaryDto dto) { + dto.setId(post.getId()); + dto.setTitle(post.getTitle()); + dto.setContent(post.getContent()); + dto.setCreatedAt(post.getCreatedAt()); + dto.setAuthor(toAuthorDto(post.getAuthor())); + dto.setCategory(toCategoryDto(post.getCategory())); + dto.setTags(post.getTags().stream().map(this::toTagDto).collect(Collectors.toList())); + dto.setViews(post.getViews()); + dto.setStatus(post.getStatus()); + dto.setPinnedAt(post.getPinnedAt()); + + List reactions = reactionService.getReactionsForPost(post.getId()) + .stream() + .map(this::toReactionDto) + .collect(Collectors.toList()); + dto.setReactions(reactions); + + List participants = commentService.getParticipants(post.getId(), 5); + dto.setParticipants(participants.stream().map(this::toAuthorDto).collect(Collectors.toList())); + + LocalDateTime last = commentService.getLastCommentTime(post.getId()); + dto.setLastReplyAt(last != null ? last : post.getCreatedAt()); + dto.setReward(0); + dto.setSubscribed(false); + } + + private CommentDto toCommentDtoWithReplies(Comment comment) { + CommentDto dto = toCommentDto(comment); + List replies = commentService.getReplies(comment.getId()).stream() + .map(this::toCommentDtoWithReplies) + .collect(Collectors.toList()); + dto.setReplies(replies); + + List reactions = reactionService.getReactionsForComment(comment.getId()) + .stream() + .map(this::toReactionDto) + .collect(Collectors.toList()); + dto.setReactions(reactions); + + return dto; + } + + private CommentDto toCommentDto(Comment comment) { + CommentDto dto = new CommentDto(); + dto.setId(comment.getId()); + dto.setContent(comment.getContent()); + dto.setCreatedAt(comment.getCreatedAt()); + dto.setAuthor(toAuthorDto(comment.getAuthor())); + dto.setReward(0); + return dto; + } + + private ReactionDto toReactionDto(Reaction reaction) { + ReactionDto dto = new ReactionDto(); + dto.setId(reaction.getId()); + dto.setType(reaction.getType()); + dto.setUser(reaction.getUser().getUsername()); + if (reaction.getPost() != null) { + dto.setPostId(reaction.getPost().getId()); + } + if (reaction.getComment() != null) { + dto.setCommentId(reaction.getComment().getId()); + } + dto.setReward(0); + return dto; + } + + private CategoryDto toCategoryDto(Category category) { + CategoryDto dto = new CategoryDto(); + dto.setId(category.getId()); + dto.setName(category.getName()); + dto.setDescription(category.getDescription()); + dto.setIcon(category.getIcon()); + dto.setSmallIcon(category.getSmallIcon()); + return dto; + } + + private TagDto toTagDto(Tag tag) { + TagDto dto = new TagDto(); + dto.setId(tag.getId()); + dto.setName(tag.getName()); + dto.setDescription(tag.getDescription()); + dto.setIcon(tag.getIcon()); + dto.setSmallIcon(tag.getSmallIcon()); + return dto; + } + + private AuthorDto toAuthorDto(User user) { + AuthorDto dto = new AuthorDto(); + dto.setId(user.getId()); + dto.setUsername(user.getUsername()); + dto.setAvatar(user.getAvatar()); + return dto; + } +} +