Skip to content

Commit 79e12d7

Browse files
committed
Feat: 게시글 생성 API 구현
1 parent cfa8a0e commit 79e12d7

File tree

11 files changed

+244
-0
lines changed

11 files changed

+244
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.back.domain.board.controller;
2+
3+
import com.back.domain.board.dto.PostRequest;
4+
import com.back.domain.board.dto.PostResponse;
5+
import com.back.domain.board.service.PostService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.user.CustomUserDetails;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
@RestController
16+
@RequestMapping("/api/posts")
17+
@RequiredArgsConstructor
18+
public class PostController {
19+
private final PostService postService;
20+
21+
// 게시글 생성
22+
@PostMapping
23+
public ResponseEntity<RsData<PostResponse>> createPost(
24+
@RequestBody @Valid PostRequest request,
25+
@AuthenticationPrincipal CustomUserDetails user
26+
) {
27+
PostResponse response = postService.createPost(request, user.getUserId());
28+
return ResponseEntity
29+
.status(HttpStatus.CREATED)
30+
.body(RsData.success(
31+
"게시글이 생성되었습니다.",
32+
response
33+
));
34+
}
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.back.domain.board.dto;
2+
3+
import com.back.domain.user.entity.User;
4+
5+
/**
6+
* 작성자 응답 DTO
7+
*
8+
* @param id 작성자 ID
9+
* @param nickname 작성자 닉네임
10+
*/
11+
public record AuthorResponse(
12+
Long id,
13+
String nickname
14+
) {
15+
public static AuthorResponse from(User user) {
16+
return new AuthorResponse(
17+
user.getId(),
18+
user.getUserProfile().getNickname()
19+
);
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.back.domain.board.dto;
2+
3+
import com.back.domain.board.entity.PostCategory;
4+
5+
/**
6+
* 카테고리 응답 DTO
7+
*
8+
* @param id 카테고리 ID
9+
* @param name 카테고리 이름
10+
*/
11+
public record CategoryResponse(
12+
Long id,
13+
String name
14+
) {
15+
public static CategoryResponse from(PostCategory category) {
16+
return new CategoryResponse(
17+
category.getId(),
18+
category.getName()
19+
);
20+
}
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.back.domain.board.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
5+
import java.util.List;
6+
7+
/**
8+
* 게시글 생성 및 수정을 위한 요청 DTO
9+
*
10+
* @param title 게시글 제목
11+
* @param content 게시글 내용
12+
* @param categoryIds 카테고리 ID 리스트
13+
*/
14+
public record PostRequest(
15+
@NotBlank String title,
16+
@NotBlank String content,
17+
List<Long> categoryIds
18+
) {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.back.domain.board.dto;
2+
3+
import com.back.domain.board.entity.Post;
4+
import java.time.LocalDateTime;
5+
import java.util.List;
6+
7+
/**
8+
* 게시글 응답 DTO
9+
*
10+
* @param postId 게시글 ID
11+
* @param author 작성자 정보
12+
* @param title 게시글 제목
13+
* @param content 게시글 내용
14+
* @param categories 게시글 카테고리 목록
15+
* @param createdAt 게시글 생성 일시
16+
* @param updatedAt 게시글 수정 일시
17+
*/
18+
public record PostResponse(
19+
Long postId,
20+
AuthorResponse author,
21+
String title,
22+
String content,
23+
List<CategoryResponse> categories,
24+
LocalDateTime createdAt,
25+
LocalDateTime updatedAt
26+
) {
27+
public static PostResponse from(Post post) {
28+
return new PostResponse(
29+
post.getId(),
30+
AuthorResponse.from(post.getUser()),
31+
post.getTitle(),
32+
post.getContent(),
33+
post.getCategories().stream()
34+
.map(CategoryResponse::from)
35+
.toList(),
36+
post.getCreatedAt(),
37+
post.getUpdatedAt()
38+
);
39+
}
40+
}

src/main/java/com/back/domain/board/entity/Post.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,28 @@ public class Post extends BaseEntity {
3232

3333
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
3434
private List<Comment> comments = new ArrayList<>();
35+
36+
// -------------------- 생성자 --------------------
37+
public Post(User user, String title, String content) {
38+
this.user = user;
39+
this.title = title;
40+
this.content = content;
41+
}
42+
43+
// -------------------- 비즈니스 메서드 --------------------
44+
// 카테고리 업데이트
45+
public void updateCategories(List<PostCategory> categories) {
46+
this.postCategoryMappings.clear();
47+
categories.forEach(category ->
48+
this.postCategoryMappings.add(new PostCategoryMapping(this, category))
49+
);
50+
}
51+
52+
// -------------------- 헬퍼 메서드 --------------------
53+
// 게시글에 연결된 카테고리 목록 조회
54+
public List<PostCategory> getCategories() {
55+
return postCategoryMappings.stream()
56+
.map(PostCategoryMapping::getCategory)
57+
.toList();
58+
}
3559
}

src/main/java/com/back/domain/board/entity/PostCategoryMapping.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,10 @@ public class PostCategoryMapping {
1818
@ManyToOne(fetch = FetchType.LAZY)
1919
@JoinColumn(name = "category_id")
2020
private PostCategory category;
21+
22+
// -------------------- 생성자 --------------------
23+
public PostCategoryMapping(Post post, PostCategory category) {
24+
this.post = post;
25+
this.category = category;
26+
}
2127
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.back.domain.board.repository;
2+
3+
import com.back.domain.board.entity.PostCategory;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface PostCategoryRepository extends JpaRepository<PostCategory, Long> {
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.back.domain.board.repository;
2+
3+
import com.back.domain.board.entity.Post;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface PostRepository extends JpaRepository<Post, Long> {
9+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.back.domain.board.service;
2+
3+
import com.back.domain.board.dto.PostRequest;
4+
import com.back.domain.board.dto.PostResponse;
5+
import com.back.domain.board.entity.Post;
6+
import com.back.domain.board.entity.PostCategory;
7+
import com.back.domain.board.repository.PostCategoryRepository;
8+
import com.back.domain.board.repository.PostRepository;
9+
import com.back.domain.user.entity.User;
10+
import com.back.domain.user.repository.UserRepository;
11+
import com.back.global.exception.CustomException;
12+
import com.back.global.exception.ErrorCode;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.stereotype.Service;
15+
import org.springframework.transaction.annotation.Transactional;
16+
17+
import java.util.List;
18+
19+
@Service
20+
@RequiredArgsConstructor
21+
@Transactional
22+
public class PostService {
23+
private final PostRepository postRepository;
24+
private final UserRepository userRepository;
25+
private final PostCategoryRepository postCategoryRepository;
26+
27+
/**
28+
* 게시글 생성 서비스
29+
* 1. User 조회
30+
* 2. Post 생성
31+
* 3. Category 매핑
32+
* 4. Post 저장 및 PostResponse 반환
33+
*/
34+
public PostResponse createPost(PostRequest request, Long userId) {
35+
36+
// User 조회
37+
User user = userRepository.findById(userId)
38+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
39+
40+
// Post 생성
41+
Post post = new Post(user, request.title(), request.content());
42+
43+
// Category 매핑
44+
if (request.categoryIds() != null) {
45+
List<PostCategory> categories = postCategoryRepository.findAllById(request.categoryIds());
46+
if (categories.size() != request.categoryIds().size()) {
47+
throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND);
48+
}
49+
post.updateCategories(categories);
50+
}
51+
52+
// Post 저장 및 응답 반환
53+
Post saved = postRepository.save(post);
54+
return PostResponse.from(saved);
55+
}
56+
}

0 commit comments

Comments
 (0)