Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public interface PostControllerDocs {
{ "id": 1, "name": "공지사항" },
{ "id": 2, "name": "자유게시판" }
],
"images": [
{ "id": 11, "url": "https://example.com/image1.png" },
{ "id": 12, "url": "https://example.com/image2.png" }
],
"createdAt": "2025-09-22T10:30:00",
"updatedAt": "2025-09-22T10:30:00"
}
Expand Down Expand Up @@ -112,7 +116,7 @@ public interface PostControllerDocs {
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 사용자 또는 카테고리",
description = "존재하지 않는 리소스 (사용자, 카테고리, 파일)",
content = @Content(
mediaType = "application/json",
examples = {
Expand All @@ -131,6 +135,14 @@ public interface PostControllerDocs {
"message": "존재하지 않는 카테고리입니다.",
"data": null
}
"""),
@ExampleObject(name = "존재하지 않는 파일(이미지)", value = """
{
"success": false,
"code": "FILE_004",
"message": "파일 정보를 찾을 수 없습니다.",
"data": null
}
""")
}
)
Expand Down Expand Up @@ -252,14 +264,22 @@ ResponseEntity<RsData<PageResponse<PostListResponse>>> getPosts(
"message": "게시글이 조회되었습니다.",
"data": {
"postId": 101,
"author": { "id": 5, "nickname": "홍길동", "profileImageUrl": null },
"author": {
"id": 5,
"nickname": "홍길동",
"profileImageUrl": null
},
"title": "첫 번째 게시글",
"content": "안녕하세요, 첫 글입니다!",
"thumbnailUrl": null,
"categories": [
{ "id": 1, "name": "공지사항" },
{ "id": 2, "name": "자유게시판" }
],
"images": [
{ "id": 11, "url": "https://example.com/image1.png" },
{ "id": 12, "url": "https://example.com/image2.png" }
],
"likeCount": 10,
"bookmarkCount": 2,
"commentCount": 3,
Expand Down Expand Up @@ -337,8 +357,12 @@ ResponseEntity<RsData<PostDetailResponse>> getPost(
{ "id": 1, "name": "공지사항" },
{ "id": 2, "name": "자유게시판" }
],
"images": [
{ "id": 11, "url": "https://example.com/image1.png" },
{ "id": 12, "url": "https://example.com/image2.png" }
],
"createdAt": "2025-09-22T10:30:00",
"updatedAt": "2025-09-22T10:30:00"
"updatedAt": "2025-09-22T10:45:00"
}
}
""")
Expand Down Expand Up @@ -409,7 +433,7 @@ ResponseEntity<RsData<PostDetailResponse>> getPost(
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 사용자/게시글/카테고리",
description = "존재하지 않는 리소스 (사용자, 게시글, 카테고리, 파일)",
content = @Content(
mediaType = "application/json",
examples = {
Expand All @@ -436,6 +460,14 @@ ResponseEntity<RsData<PostDetailResponse>> getPost(
"message": "존재하지 않는 카테고리입니다.",
"data": null
}
"""),
@ExampleObject(name = "존재하지 않는 파일(이미지)", value = """
{
"success": false,
"code": "FILE_004",
"message": "파일 정보를 찾을 수 없습니다.",
"data": null
}
""")
}
)
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/back/domain/board/post/dto/ImageResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.back.domain.board.post.dto;

import com.back.domain.file.entity.FileAttachment;

/**
* 이미지 응답 DTO
*
* @param id 이미지 ID
* @param url 이미지 URL
*/
public record ImageResponse(
Long id,
String url
) {
public static ImageResponse from(FileAttachment fileAttachment) {
return new ImageResponse(
fileAttachment.getId(),
fileAttachment.getPublicURL()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.back.domain.board.common.dto.AuthorResponse;
import com.back.domain.board.post.entity.Post;
import com.back.domain.file.entity.FileAttachment;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -15,6 +16,7 @@
* @param content 게시글 내용
* @param thumbnailUrl 썸네일 URL
* @param categories 게시글 카테고리 목록
* @param images 첨부된 이미지 목록
* @param likeCount 좋아요 수
* @param bookmarkCount 북마크 수
* @param commentCount 댓글 수
Expand All @@ -30,6 +32,7 @@ public record PostDetailResponse(
String content,
String thumbnailUrl,
List<CategoryResponse> categories,
List<ImageResponse> images,
long likeCount,
long bookmarkCount,
long commentCount,
Expand All @@ -38,11 +41,11 @@ public record PostDetailResponse(
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static PostDetailResponse from(Post post) {
return from(post, false, false);
public static PostDetailResponse from(Post post, List<FileAttachment> attachments) {
return from(post, attachments, false, false);
}

public static PostDetailResponse from(Post post, boolean likedByMe, boolean bookmarkedByMe) {
public static PostDetailResponse from(Post post, List<FileAttachment> attachments, boolean likedByMe, boolean bookmarkedByMe) {
return new PostDetailResponse(
post.getId(),
AuthorResponse.from(post.getUser()),
Expand All @@ -52,6 +55,9 @@ public static PostDetailResponse from(Post post, boolean likedByMe, boolean book
post.getCategories().stream()
.map(CategoryResponse::from)
.toList(),
attachments.stream()
.map(ImageResponse::from)
.toList(),
post.getPostLikes().size(),
post.getPostBookmarks().size(),
post.getComments().size(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
* @param content 게시글 내용
* @param thumbnailUrl 썸네일 URL
* @param categoryIds 카테고리 ID 리스트
* @param imageIds 이미지 ID 리스트
*/
public record PostRequest(
@NotBlank String title,
@NotBlank String content,
String thumbnailUrl,
List<Long> categoryIds
List<Long> categoryIds,
List<Long> imageIds
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.back.domain.board.common.dto.AuthorResponse;
import com.back.domain.board.post.entity.Post;
import com.back.domain.file.entity.FileAttachment;

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

Expand All @@ -14,6 +16,7 @@
* @param content 게시글 내용
* @param thumbnailUrl 썸네일 URL
* @param categories 게시글 카테고리 목록
* @param images 첨부된 이미지 목록
* @param createdAt 게시글 생성 일시
* @param updatedAt 게시글 수정 일시
*/
Expand All @@ -24,10 +27,11 @@ public record PostResponse(
String content,
String thumbnailUrl,
List<CategoryResponse> categories,
List<ImageResponse> images,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static PostResponse from(Post post) {
public static PostResponse from(Post post, List<FileAttachment> attachments) {
return new PostResponse(
post.getId(),
AuthorResponse.from(post.getUser()),
Expand All @@ -37,6 +41,9 @@ public static PostResponse from(Post post) {
post.getCategories().stream()
.map(CategoryResponse::from)
.toList(),
attachments.stream()
.map(ImageResponse::from)
.toList(),
post.getCreatedAt(),
post.getUpdatedAt()
);
Expand Down
67 changes: 56 additions & 11 deletions src/main/java/com/back/domain/board/post/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
import com.back.domain.board.post.dto.PostListResponse;
import com.back.domain.board.post.dto.PostRequest;
import com.back.domain.board.post.dto.PostResponse;
import com.back.domain.board.post.entity.PostCategoryMapping;
import com.back.domain.board.post.repository.PostBookmarkRepository;
import com.back.domain.board.post.repository.PostCategoryRepository;
import com.back.domain.board.post.repository.PostLikeRepository;
import com.back.domain.board.post.repository.PostRepository;
import com.back.domain.file.entity.AttachmentMapping;
import com.back.domain.file.entity.EntityType;
import com.back.domain.file.entity.FileAttachment;
import com.back.domain.file.repository.AttachmentMappingRepository;
import com.back.domain.file.repository.FileAttachmentRepository;
import com.back.domain.user.common.entity.User;
import com.back.domain.user.common.repository.UserRepository;
import com.back.global.exception.CustomException;
Expand All @@ -32,6 +38,8 @@ public class PostService {
private final PostBookmarkRepository postBookmarkRepository;
private final UserRepository userRepository;
private final PostCategoryRepository postCategoryRepository;
private final FileAttachmentRepository fileAttachmentRepository;
private final AttachmentMappingRepository attachmentMappingRepository;

/**
* 게시글 생성 서비스
Expand All @@ -47,15 +55,24 @@ public PostResponse createPost(PostRequest request, Long userId) {

// Post 생성
Post post = new Post(user, request.title(), request.content(), request.thumbnailUrl());
Post saved = postRepository.save(post);
postRepository.save(post);

// Category 매핑
if (request.categoryIds() != null) {
List<PostCategory> categories = validateAndFindCategories(request.categoryIds());
saved.updateCategories(categories);
categories.forEach(category -> new PostCategoryMapping(post, category));
}

return PostResponse.from(saved);
// AttachmentMapping 매핑
List<FileAttachment> attachments = List.of();
if (request.imageIds() != null && !request.imageIds().isEmpty()) {
attachments = validateAndFindAttachments(request.imageIds());
for (FileAttachment attachment : attachments) {
attachmentMappingRepository.save(new AttachmentMapping(attachment, EntityType.POST, post.getId()));
}
}

return PostResponse.from(post, attachments);
}

/**
Expand Down Expand Up @@ -86,15 +103,22 @@ public PostDetailResponse getPost(Long postId, Long userId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));

// 첨부파일 조회
List<FileAttachment> attachments = attachmentMappingRepository
.findAllByEntityTypeAndEntityId(EntityType.POST, post.getId())
.stream()
.map(AttachmentMapping::getFileAttachment)
.toList();

// 로그인 사용자 추가 데이터 설정 (좋아요, 북마크 여부)
if (userId != null) {
boolean likedByMe = postLikeRepository.existsByUserIdAndPostId(userId, post.getId());
boolean bookmarkedByMe = postBookmarkRepository.existsByUserIdAndPostId(userId, post.getId());
return PostDetailResponse.from(post, likedByMe, bookmarkedByMe);
return PostDetailResponse.from(post, attachments, likedByMe, bookmarkedByMe);
}

// 비로그인 사용자는 기본 응답 반환
return PostDetailResponse.from(post);
return PostDetailResponse.from(post, attachments);
}

/**
Expand Down Expand Up @@ -126,7 +150,17 @@ public PostResponse updatePost(Long postId, PostRequest request, Long userId) {
List<PostCategory> categories = validateAndFindCategories(request.categoryIds());
post.updateCategories(categories);

return PostResponse.from(post);
// AttachmentMapping 매핑
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(EntityType.POST, post.getId());
List<FileAttachment> attachments = List.of();
if (request.imageIds() != null && !request.imageIds().isEmpty()) {
attachments = validateAndFindAttachments(request.imageIds());
attachments.forEach(attachment ->
attachmentMappingRepository.save(new AttachmentMapping(attachment, EntityType.POST, post.getId()))
);
}

return PostResponse.from(post, attachments);
}

/**
Expand All @@ -149,6 +183,9 @@ public void deletePost(Long postId, Long userId) {
throw new CustomException(ErrorCode.POST_NO_PERMISSION);
}

// AttachmentMapping 매핑 제거
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(EntityType.POST, post.getId());

// Post 삭제
post.remove();
postRepository.delete(post);
Expand All @@ -158,11 +195,19 @@ public void deletePost(Long postId, Long userId) {
* 카테고리 ID 유효성 검증 및 조회
*/
private List<PostCategory> validateAndFindCategories(List<Long> categoryIds) {
List<PostCategory> categories = postCategoryRepository.findAllById(categoryIds);
return categoryIds.stream()
.map(id -> postCategoryRepository.findById(id)
.orElseThrow(() -> new CustomException(ErrorCode.CATEGORY_NOT_FOUND)))
.toList();
}

if (categories.size() != categoryIds.size()) {
throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND);
}
return categories;
/**
* 첨부 파일 ID 유효성 검증 및 조회
*/
private List<FileAttachment> validateAndFindAttachments(List<Long> imageIds) {
return imageIds.stream()
.map(id -> fileAttachmentRepository.findById(id)
.orElseThrow(() -> new CustomException(ErrorCode.FILE_NOT_FOUND)))
.toList();
}
}
Loading