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
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## TODO List

### 공통
- [X] JPA Audit 기능 설정
- [X] 데이터 변경 시점에 대한 추적/감사를 위한 메타 데이터 필드 구성

### 게시글
- [X] 게시글 작성 API 구현
- [X] 게시글 작성 서비스 구현
- [X] 입력된 필드 검증 후 예외 처리
- [X] 게시글 수정 API 구현
- [X] 게시글 수정 시 예외 케이스 구현
- [X] 존재하지 않는 게시글 수정 시 예외 처리
- [X] 입력된 필드 검증 후 예외 처리
- [X] 게시글 수정 서비스 구현
- [X] 게시글 삭제 API 구현
- [X] 게시글 삭제 시 예외 케이스 구현
- [X] 존재하지 않는 게시글 삭제 요청 시 예외 처리
- [X] 게시글 삭제 서비스 구현
- [X] 게시글 조회 API 구현
- [X] 게시글 단건 조회 서비스 구현
- [X] 게시글 전체 조회 서비스 구현
- [X] 단건 조회 시 댓글 목록까지 조회

### 댓글
- [X] 댓글 작성 API 구현
- [X] 존재하지 않는 게시글에 댓글 작성 시 예외 처리
- [X] 댓글 수정 API 구현
- [X] 존재하지 않는 댓글 수정 시 예외 처리
- [X] 댓글 삭제 API 구현
- [X] 존재하지 않는 댓글 삭제 시 예외 처리


...
---
## 리팩토링
- [X] 공통 예외/응답처리 코드 만들기
- [ ] 댓글 페이징 기능


## 구현 시 어렵거나 이해가 되지 않는 부분
- .
-
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.postgresql:postgresql:42.7.2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
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
47 changes: 47 additions & 0 deletions src/main/java/io/sparta/board/controller/CommentController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.sparta.board.controller;

import io.sparta.board.dto.ApiResponse;
import io.sparta.board.dto.comment.CommentRequestDto;
import io.sparta.board.dto.comment.CommentResponseDto;
import io.sparta.board.service.CommentService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

@RestController
@RequestMapping("/api/posts")
public class CommentController {
private final CommentService commentService;

public CommentController(CommentService commentService) {
this.commentService = commentService;
}

// 작성
@PostMapping("/{postId}/comments")
public ResponseEntity<ApiResponse<CommentResponseDto>> addComment(@PathVariable UUID postId, @RequestBody @Valid CommentRequestDto requestDto) {
CommentResponseDto response = commentService.addComment(postId, requestDto);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 수정
@PutMapping("/{postId}/comments/{commentId}")
public ResponseEntity<ApiResponse<CommentResponseDto>> updateComment(
@PathVariable UUID postId,
@PathVariable UUID commentId,
@RequestBody @Valid CommentRequestDto requestDto) {
CommentResponseDto response = commentService.updateComment(postId, commentId, requestDto);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 삭제
@DeleteMapping("/{postId}/comments/{commentId}")
public ResponseEntity<ApiResponse<UUID>> deleteComment(
@PathVariable UUID postId,
@PathVariable UUID commentId) {
UUID deletedId = commentService.deleteComment(postId, commentId);
return ResponseEntity.ok(ApiResponse.success(deletedId));
}
}
58 changes: 58 additions & 0 deletions src/main/java/io/sparta/board/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.sparta.board.controller;

import io.sparta.board.dto.ApiResponse;
import io.sparta.board.dto.post.PostDetailResponseDto;
import io.sparta.board.dto.post.PostRequestDto;
import io.sparta.board.dto.post.PostResponseDto;
import io.sparta.board.service.PostService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.UUID;

@RestController
@RequestMapping("/api")
public class PostController {
private final PostService postService;

public PostController(PostService postService) {
this.postService = postService;
}

// 작성
@PostMapping("/posts")
public ResponseEntity<ApiResponse<PostResponseDto>> createPost(@RequestBody @Valid PostRequestDto requestDto) {
PostResponseDto response = postService.createPost(requestDto);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 수정
@PutMapping("/posts/{id}")
public ResponseEntity<ApiResponse<PostResponseDto>> updatePost(@PathVariable UUID id, @RequestBody @Valid PostRequestDto requestDto) {
PostResponseDto response = postService.updatePost(id, requestDto);
return ResponseEntity.ok(ApiResponse.success(response));
}

// 삭제
@DeleteMapping("/posts/{id}")
public ResponseEntity<ApiResponse<UUID>> deletePost(@PathVariable UUID id) {
UUID deletedId = postService.deletePost(id);
return ResponseEntity.ok(ApiResponse.success(deletedId));
}

// 전체 조회
@GetMapping("/posts")
public ResponseEntity<ApiResponse<List<PostResponseDto>>> getAllPosts() {
List<PostResponseDto> response = postService.getAllPosts();
return ResponseEntity.ok(ApiResponse.success(response));
}

// 단건 조회
@GetMapping("/posts/{id}")
public ResponseEntity<ApiResponse<PostDetailResponseDto>> getPost(@PathVariable UUID id) {
PostDetailResponseDto response = postService.getPost(id);
return ResponseEntity.ok(ApiResponse.success(response));
}
}
24 changes: 24 additions & 0 deletions src/main/java/io/sparta/board/dto/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.sparta.board.dto;

import lombok.Getter;

@Getter
public class ApiResponse<T> {
private final boolean success;
private final T data;
private final String error;

private ApiResponse(boolean success, T data, String error) {
this.success = success;
this.data = data;
this.error = error;
}

public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, data, null);
}

public static <T> ApiResponse<T> error(String errorMessage) {
return new ApiResponse<>(false, null, errorMessage);
}
}
10 changes: 10 additions & 0 deletions src/main/java/io/sparta/board/dto/comment/CommentRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.sparta.board.dto.comment;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;

@Getter
public class CommentRequestDto {
@NotBlank
private String content;
}
33 changes: 33 additions & 0 deletions src/main/java/io/sparta/board/dto/comment/CommentResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.sparta.board.dto.comment;

import io.sparta.board.entity.Comment;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter
public class CommentResponseDto {
private final UUID id;
private final String content;
private final LocalDateTime createdAt;
private final LocalDateTime updatedAt;

@Builder
public CommentResponseDto(UUID id, String content, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.content = content;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

public static CommentResponseDto from(Comment comment) {
return CommentResponseDto.builder()
.id(comment.getId())
.content(comment.getContent())
.createdAt(comment.getCreatedAt())
.updatedAt(comment.getUpdatedAt())
.build();
}
}
41 changes: 41 additions & 0 deletions src/main/java/io/sparta/board/dto/post/PostDetailResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.sparta.board.dto.post;

import io.sparta.board.dto.comment.CommentResponseDto;
import io.sparta.board.entity.Post;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;

@Getter
public class PostDetailResponseDto {
private final UUID id;
private final String title;
private final String content;
private final LocalDateTime createdAt;
private final LocalDateTime updatedAt;
private List<CommentResponseDto> comments;

@Builder
public PostDetailResponseDto(UUID id, String title, String content, LocalDateTime createdAt, LocalDateTime updatedAt, List<CommentResponseDto> comments) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.comments = comments;
}

public static PostDetailResponseDto from(Post post, List<CommentResponseDto> comments) {
return PostDetailResponseDto.builder()
.id(post.getId())
.title(post.getTitle())
.content(post.getContent())
.createdAt(post.getCreatedAt())
.updatedAt(post.getUpdatedAt())
.comments(comments)
.build();
}
}
12 changes: 12 additions & 0 deletions src/main/java/io/sparta/board/dto/post/PostRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.sparta.board.dto.post;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;

@Getter
public class PostRequestDto {
@NotBlank
private String title;
@NotBlank
private String content;
}
37 changes: 37 additions & 0 deletions src/main/java/io/sparta/board/dto/post/PostResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.sparta.board.dto.post;

import io.sparta.board.entity.Post;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter
public class PostResponseDto {
private final UUID id;
private final String title;
private final String content;
private final LocalDateTime createdAt;
private final LocalDateTime updatedAt;

@Builder
public PostResponseDto(UUID id, String title, String content, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

// 정적 팩토리 메서드: Post 엔티티로부터 DTO 생성
public static PostResponseDto from(Post post) {
return PostResponseDto.builder()
.id(post.getId())
.title(post.getTitle())
.content(post.getContent())
.createdAt(post.getCreatedAt())
.updatedAt(post.getUpdatedAt())
.build();
}
}
35 changes: 35 additions & 0 deletions src/main/java/io/sparta/board/entity/BaseEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.sparta.board.entity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;

@LastModifiedDate
@Column
private LocalDateTime updatedAt;

@Column
private Boolean deleted = false;

@Column
private LocalDateTime deletedAt;

public void softDelete() {
deleted = true;
deletedAt = LocalDateTime.now();
}
}
Loading