Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
0d4c2e4
docs: README 작성
Yang-Sooyoung Apr 1, 2025
90958b0
feat: Post Entity, Repository 작성, BaseEntity 작성
Yang-Sooyoung Apr 1, 2025
6b7ab72
feat: JpaAudit 작성
Yang-Sooyoung Apr 1, 2025
af3416a
feat: Post Create Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
d37c076
feat: Post Create Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
f520152
feat: Post Create Controller 작성
Yang-Sooyoung Apr 1, 2025
33469f4
feat: Post Create Test 작성
Yang-Sooyoung Apr 1, 2025
5e06430
feat: Post Update Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
cc78cdc
feat: Post Update Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
5c10533
feat: Post Update Controller 작성
Yang-Sooyoung Apr 1, 2025
27e43ca
feat: Post Update Entity 수정
Yang-Sooyoung Apr 1, 2025
52a79c7
feat: Post Update Test 작성
Yang-Sooyoung Apr 1, 2025
f5e0e67
feat: Post Delete Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
0fcfa1b
feat: Post Delete Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
1985400
feat: Post Delete Controller 작성
Yang-Sooyoung Apr 1, 2025
9a13662
feat: Post Delete BaseEntity 수정
Yang-Sooyoung Apr 1, 2025
2d75a9f
feat: Post Delete Test 작성
Yang-Sooyoung Apr 1, 2025
a0add9b
feat: Comment Create Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
72bcef3
feat: Comment Create Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
e0b4e34
feat: Comment Create Controller 작성
Yang-Sooyoung Apr 1, 2025
e7f5b6a
feat: Comment Create Test 작성
Yang-Sooyoung Apr 1, 2025
7500513
feat: Comment Entity, Repository 작성
Yang-Sooyoung Apr 1, 2025
e9c6515
feat: Comment Update Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
163909c
feat: Comment Update Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
cdbb4b9
feat: Comment Update Controller 작성
Yang-Sooyoung Apr 1, 2025
2b1cc5f
feat: Comment Update Entity 수정
Yang-Sooyoung Apr 1, 2025
c829f31
feat: Comment Update Test 작성
Yang-Sooyoung Apr 1, 2025
a7b30a5
feat: Comment Delete Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
a5d8e1d
feat: Comment Delete Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
ff08653
feat: Comment Delete Controller 작성
Yang-Sooyoung Apr 1, 2025
d039c18
feat: Comment Delete Test 작성
Yang-Sooyoung Apr 1, 2025
82c0c31
feat: Post Read Facade, Usecase 작성
Yang-Sooyoung Apr 1, 2025
daf5d8c
feat: Post Read Dto, Mapper 작성
Yang-Sooyoung Apr 1, 2025
8f46adc
feat: Post Read Controller 작성
Yang-Sooyoung Apr 1, 2025
3ae0058
feat: Post Read Query Repository, Config 작성
Yang-Sooyoung Apr 1, 2025
54e4b84
feat: Post Read Test 작성
Yang-Sooyoung Apr 1, 2025
cec9892
feat: build.gradle querydsl 추가
Yang-Sooyoung Apr 1, 2025
e2dfd6e
docs: README 수정
Yang-Sooyoung Apr 1, 2025
e1f725e
feat: @Transactional 추가
Yang-Sooyoung Apr 2, 2025
0c6ba8a
feat: Entity nullable 추가, 자료형 수정
Yang-Sooyoung Apr 2, 2025
5e69235
feat: Controller path 수정
Yang-Sooyoung Apr 2, 2025
0b4a867
feat: Post Read Mapper 수정
Yang-Sooyoung Apr 2, 2025
368f6ba
feat: Post Read Query Repository 수정
Yang-Sooyoung Apr 2, 2025
16cd48b
feat: Board Test path 수정
Yang-Sooyoung Apr 2, 2025
9a7a7d0
feat: build.gradle valid 추가
Yang-Sooyoung Apr 2, 2025
8ce243d
feat: Post Valid 작성
Yang-Sooyoung Apr 2, 2025
e656b9b
feat: Comment Valid 작성
Yang-Sooyoung Apr 2, 2025
7ad6b65
feat: Exception 작성
Yang-Sooyoung Apr 2, 2025
f09514c
feat: Post Exception 작성
Yang-Sooyoung Apr 2, 2025
6e781a5
feat: Comment Exception 작성
Yang-Sooyoung Apr 2, 2025
937cbd5
fix: 공백 제거
Yang-Sooyoung Apr 2, 2025
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
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
## TODO List

- [ ] 공통 처리
- [ ] BaseEntity 작성
- [ ] JpaAudit 작성


- [ ] 게시글 등록 API
- [ ] Entity 작성
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 게시글 수정 API 구현
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 게시글 삭제 API 구현
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 댓글 등록 API 구현
- [ ] Entity 작성
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 댓글 수정 API 구현
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 댓글 삭제 API 구현
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 게시글 단건 조희 API 구현
- [ ] Facade, Usecase 작성
- [ ] Create Dto, Mapper 작성
- [ ] QueryDsl 작성
- [ ] Controller 작성
- [ ] Test 작성


- [ ] 공통 처리
- [ ] soft-delete 처리
- [ ] valid 처리
- [ ] exception 처리

...

---
## 도전 과제
- 게시글 조회 시 해당 게시글의 댓글 목록 페이징 처리
- Pagination 시 요청 Page Size가 10/30/50이 아닌 경우 10으로 고정하세요

## 추후 학습 & 이해가지 않는 부분
- Jpa Audit 동작 원리
- QueryDsl 동작 원리
- @ManyToOne 옵션 사용해야할 때
- ENUM, Implements, mapper 사용해야 할 때
- LeftJoin 해야할까?
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ repositories {
}

dependencies {
// Query DSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/sparta/board/BoardApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class BoardApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.sparta.board.app.domain.comment.application.facade;

import io.sparta.board.app.domain.comment.presentation.dto.request.CommentCreateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentCreateResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentDeleteResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentUpdateResponseDto;
import java.util.UUID;

public interface CommentFacade {
CommentCreateResponseDto createComment(CommentCreateRequestDto commentCreateRequestDto);

CommentUpdateResponseDto updateComment(UUID id, CommentUpdateRequestDto commentUpdateRequestDto);

CommentDeleteResponseDto deleteComment(UUID id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.sparta.board.app.domain.comment.application.facade;

import io.sparta.board.app.domain.comment.application.usecase.CommentService;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentCreateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentCreateResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentDeleteResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentUpdateResponseDto;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class CommentFacadeImpl implements CommentFacade {
private final CommentService commentService;

@Override
public CommentCreateResponseDto createComment(CommentCreateRequestDto commentCreateRequestDto) {
return commentService.createComment(commentCreateRequestDto);
}

@Override
public CommentUpdateResponseDto updateComment(UUID id,
CommentUpdateRequestDto commentUpdateRequestDto) {
return commentService.updateComment(id, commentUpdateRequestDto);
}

@Override
public CommentDeleteResponseDto deleteComment(UUID id) {
return commentService.deleteComment(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.sparta.board.app.domain.comment.application.usecase;

import io.sparta.board.app.domain.comment.presentation.dto.request.CommentCreateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentCreateResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentDeleteResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentUpdateResponseDto;
import java.util.UUID;

public interface CommentService {

CommentCreateResponseDto createComment(CommentCreateRequestDto commentCreateRequestDto);

CommentUpdateResponseDto updateComment(UUID id, CommentUpdateRequestDto commentUpdateRequestDto);

CommentDeleteResponseDto deleteComment(UUID id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.sparta.board.app.domain.comment.application.usecase;

import io.sparta.board.app.domain.comment.infrastructure.exception.CommentErrorCode;
import io.sparta.board.app.domain.comment.model.entity.Comment;
import io.sparta.board.app.domain.comment.model.repository.CommentRepository;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentCreateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentCreateResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentDeleteResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentUpdateResponseDto;
import io.sparta.board.app.domain.comment.presentation.mapper.CommentMapper;
import io.sparta.board.app.domain.post.infrastructure.exception.PostErrorCode;
import io.sparta.board.app.domain.post.model.entity.Post;
import io.sparta.board.app.domain.post.model.repository.PostRepository;
import io.sparta.board.app.global.exception.CustomException;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class CommentServiceImpl implements CommentService {
private final CommentRepository commentRepository;
private final PostRepository postRepository;

@Override
public CommentCreateResponseDto createComment(CommentCreateRequestDto commentCreateRequestDto) {
UUID postId = commentCreateRequestDto.getPostId();
Post post = postRepository.findById(postId).orElseThrow(() -> new CustomException(
PostErrorCode.POST_NOT_FOUND));

Comment newComment = CommentMapper.commentCreateRequestDtotoEntity(commentCreateRequestDto, post);
Comment createdComment = commentRepository.save(newComment);
return CommentMapper.entityToCreateResponseDto(createdComment);
}

@Override
public CommentUpdateResponseDto updateComment(UUID id, CommentUpdateRequestDto commentUpdateRequestDto) {
UUID postId = commentUpdateRequestDto.getPostId();
Post post = postRepository.findById(postId).orElseThrow(() -> new CustomException(PostErrorCode.POST_NOT_FOUND));

Comment comment = commentRepository.findById(id).orElseThrow(() -> new CustomException(CommentErrorCode.COMMENT_NOT_FOUND));
comment.update(post, commentUpdateRequestDto);
return CommentMapper.entityToUpdateResponseDto(post, comment);
}

@Override
public CommentDeleteResponseDto deleteComment(UUID id) {
boolean deleted = true;
Comment comment = commentRepository.findById(id).orElseThrow(() -> new CustomException(CommentErrorCode.COMMENT_NOT_FOUND));
comment.delete(deleted);

return CommentMapper.entityToDeleteResponseDto(comment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.sparta.board.app.domain.comment.infrastructure.exception;

import io.sparta.board.app.global.exception.ErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum CommentErrorCode implements ErrorCode {
COMMENT_NOT_FOUND("COMMENT_404", "존재하지 않는 댓글입니다", HttpStatus.NOT_FOUND);

private final String code;
private final String message;
private final HttpStatus status;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.sparta.board.app.domain.comment.model.entity;

import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.post.model.entity.Post;
import io.sparta.board.app.global.entity.BaseEntity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "p_comment")
@Entity
public class Comment extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "comment_id")
private UUID id;

@JoinColumn(name = "post_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private Post post;

@Column(columnDefinition = "TEXT", nullable = false)
private String content;

public void update(Post post, CommentUpdateRequestDto commentUpdateRequestDto) {
this.post = post;
this.content = commentUpdateRequestDto.getContent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.sparta.board.app.domain.comment.model.repository;

import io.sparta.board.app.domain.comment.model.entity.Comment;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentRepository extends JpaRepository<Comment, UUID> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.sparta.board.app.domain.comment.presentation.controller;

import io.sparta.board.app.domain.comment.application.facade.CommentFacade;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentCreateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.request.CommentUpdateRequestDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentCreateResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentDeleteResponseDto;
import io.sparta.board.app.domain.comment.presentation.dto.response.CommentUpdateResponseDto;
import jakarta.validation.Valid;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RestController
@RequestMapping("/api/comment")
@RequiredArgsConstructor
public class CommentController {

private final CommentFacade commentFacade;

@PostMapping
public ResponseEntity<CommentCreateResponseDto> CreateComment(@Valid @RequestBody CommentCreateRequestDto commentCreateRequestDto) {
return ResponseEntity.ok(commentFacade.createComment(commentCreateRequestDto));
}

@PatchMapping("/{id}")
public ResponseEntity<CommentUpdateResponseDto> UpdateComment(@Valid @PathVariable("id") UUID id, @RequestBody CommentUpdateRequestDto commentUpdateRequestDto) {
return ResponseEntity.ok(commentFacade.updateComment(id, commentUpdateRequestDto));
}

@DeleteMapping("/{id}")
public ResponseEntity<CommentDeleteResponseDto> DeleteComment(@PathVariable("id") UUID id) {
return ResponseEntity.ok(commentFacade.deleteComment(id));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.sparta.board.app.domain.comment.presentation.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CommentCreateRequestDto {
@NotNull(message = "게시글 ID는 필수 입력값입니다.")
@JsonProperty("post-id")
private UUID postId;

@NotNull(message = "내용은 필수 입력값입니다.")
private String content;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.sparta.board.app.domain.comment.presentation.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CommentUpdateRequestDto {
@NotNull(message = "게시글 ID는 필수 입력값입니다.")
@JsonProperty("post-id")
private UUID postId;

@NotNull(message = "내용은 필수 입력값입니다.")
private String content;
}
Loading