Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/com/catcher/app/AppApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootApplication
@ComponentScan(basePackages = {"com.catcher.core", "com.catcher.resource"})
@EnableJpaRepositories(basePackages = {"com.catcher.datasource"})
@EntityScan(basePackages = {"com.catcher.core.domain.entity"})
public class AppApplication {

public static void main(String[] args) {
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/com/catcher/core/CommandExecutor.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.catcher.core.domain.command;

public interface Command {
public interface Command<T> {
T execute();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.catcher.core.domain.command;

public interface CommandExecutor {
<T> T run(Command<T> command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.catcher.core.domain.command;

import org.springframework.stereotype.Component;

@Component
public class CommentCommandExecutor implements CommandExecutor {

@Override
public <T> T run(Command<T> command) {
return command.execute();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.catcher.core.domain.command;

import com.catcher.core.domain.entity.Comment;
import com.catcher.core.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

@RequiredArgsConstructor
public class GetCommentCommand implements Command<Page<Comment>> {

private final CommentService commentService;

private final Pageable pageable;
@Override
public Page<Comment> execute() {
return commentService.findByParentIsNull(pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.catcher.core.domain.command;

import com.catcher.core.domain.request.PostCommentRequest;
import com.catcher.core.service.CommentService;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class PostCommentCommand implements Command<Void> {

private final CommentService commentService;

private final PostCommentRequest postCommentRequest;

@Override
public Void execute() {
commentService.saveSingleComment(postCommentRequest.getUserId(), postCommentRequest.getContents());

return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.catcher.core.domain.command;

import com.catcher.core.domain.request.PostCommentReplyRequest;
import com.catcher.core.service.CommentService;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class PostCommentReplyCommand implements Command<Void> {

private final CommentService commentService;

private final PostCommentReplyRequest postCommentReplyRequest;

@Override
public Void execute() {
commentService.saveSingleReply(
postCommentReplyRequest.getParentId(),
postCommentReplyRequest.getUserId(),
postCommentReplyRequest.getContents()
);

return null;
}

}
33 changes: 33 additions & 0 deletions src/main/java/com/catcher/core/domain/entity/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.catcher.core.domain.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne
@JoinColumn(name = "parent_id")
private Comment parent;

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@Builder.Default
private List<Comment> replies = new ArrayList<>();

private Long userId;

private String contents;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.catcher.core.domain.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder // TODO: 테스트를 위해서만 필요한 경우?
@NoArgsConstructor
@AllArgsConstructor
public class PostCommentReplyRequest {

private Long userId;

private Long parentId;

private String contents;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.catcher.core.domain.request;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder // TODO: 테스트를 위해서만 필요한 경우?
@NoArgsConstructor
@AllArgsConstructor
public class PostCommentRequest {

private Long userId;

private String contents;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.catcher.core.domain.response;

import com.catcher.core.domain.entity.Comment;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Getter
@Builder
public class GetCommentsByPageResponse {

private Long id;

private String contents;

private List<GetCommentsByPageResponse> childComments;

public static List<GetCommentsByPageResponse> createGetCommentsByPageResponseList(Page<Comment> commentPage) {
Comment thread
pingu9 marked this conversation as resolved.
return commentPage
.stream()
.map(GetCommentsByPageResponse::buildRecursiveCommentResponse)
.collect(Collectors.toList());
}

private static GetCommentsByPageResponse buildRecursiveCommentResponse(Comment comment) {
GetCommentsByPageResponse response = GetCommentsByPageResponse
.builder()
.id(comment.getId())
.contents(comment.getContents())
.childComments(new ArrayList<>())
.build();

for (Comment reply : comment.getReplies()) {
response.getChildComments().add(buildRecursiveCommentResponse(reply));
}

return response;
}
}
48 changes: 48 additions & 0 deletions src/main/java/com/catcher/core/service/CommentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.catcher.core.service;

import com.catcher.core.domain.entity.Comment;
import com.catcher.datasource.CommentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class CommentService {

private final CommentRepository commentRepository;

@Transactional
public Page<Comment> findByParentIsNull(final Pageable pageable) {
return commentRepository.findByParentIsNull(pageable);
}

@Transactional
public Comment saveSingleComment(final Long userId, final String contents) {
return commentRepository.save(Comment
.builder()
.userId(userId)
.contents(contents)
.build());
}

@Transactional
public Comment saveSingleReply(Long parentId, Long userId, String contents) {
final Comment parentComment = commentRepository
.findById(parentId)
.orElseThrow(); //TODO: fill custom exception

final Comment reply = Comment
.builder()
.userId(userId)
.parent(parentComment)
.contents(contents)
.build();

parentComment.getReplies().add(reply);

return reply;
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/catcher/datasource/CommentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.catcher.datasource;

import com.catcher.core.domain.entity.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<Comment, Long> {

// TODO: N+1 join
// @Query(value = "SELECT DISTINCT c FROM Comment c LEFT OUTER JOIN FETCH c.replies WHERE c.parent IS NULL")
Page<Comment> findByParentIsNull(Pageable pageable);

}
45 changes: 45 additions & 0 deletions src/main/java/com/catcher/resource/CommentAPiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.catcher.resource;

import com.catcher.core.domain.command.*;
import com.catcher.core.domain.entity.Comment;
import com.catcher.core.domain.request.PostCommentReplyRequest;
import com.catcher.core.domain.request.PostCommentRequest;
import com.catcher.core.domain.response.GetCommentsByPageResponse;
import com.catcher.core.service.CommentService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequiredArgsConstructor
@RestController
@RequestMapping("/comment")
public class CommentAPiController {

private final CommentService commentService;

private final CommentCommandExecutor commandExecutor;

@PostMapping
public void postComment(@RequestBody PostCommentRequest postCommentRequest) {
Command<Void> command = new PostCommentCommand(commentService, postCommentRequest);
commandExecutor.run(command);
}

@PostMapping("/reply")
public void replyComment(@RequestBody PostCommentReplyRequest postCommentReplyRequest) {
Command<Void> command = new PostCommentReplyCommand(commentService, postCommentReplyRequest);
commandExecutor.run(command);
}

@GetMapping
public List<GetCommentsByPageResponse> getComments(@PageableDefault(size = 20, sort = {"id"}) Pageable pageable) {
Command<Page<Comment>> command = new GetCommentCommand(commentService, pageable);
Page<Comment> commentPage = commandExecutor.run(command);

return GetCommentsByPageResponse.createGetCommentsByPageResponseList(commentPage);
}
}
4 changes: 4 additions & 0 deletions src/test/java/com/catcher/app/AppApplicationTests.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.catcher.app;

import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@SpringBootTest
@ComponentScan(basePackages = {"com.catcher.resource"})
@EnableJpaRepositories(basePackages = {"com.catcher.datasource"})
@EntityScan(basePackages = {"com.catcher.core.domain.entity"})
class AppApplicationTests {

@Test
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/com/catcher/datasource/TestCommentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.catcher.datasource;

import com.catcher.core.domain.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

// TODO: 프로덕션 코드에는 없는 기능이 필요한 경우?
Comment thread
pingu9 marked this conversation as resolved.
public interface TestCommentRepository extends JpaRepository<Comment, Long> {

Comment findFirstByUserIdAndContentsOrderByIdDesc(Long userId, String contents);

}
Loading