Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.sejongisc.backend.board.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sejongisc.backend.board.entity.BoardType;
import org.sejongisc.backend.board.entity.PostType;
import org.sejongisc.backend.board.dto.*;
import org.sejongisc.backend.board.service.PostService;
import org.sejongisc.backend.common.auth.springsecurity.CustomUserDetails;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/post")
@Tag(
name = "게시글 및 댓글 API",
description = "게시글 및 댓글 작성, 수정, 삭제 관련 API 제공"
)
public class PostController {

private final PostService postService;

// 게시글 작성
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> createPost(
@Valid @ModelAttribute PostRequest request,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.savePost(request, userId);
return ResponseEntity.ok().build();
}

// 게시글 수정
@PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Void> updatePost(
@Valid @ModelAttribute PostRequest request,
@PathVariable UUID postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.updatePost(request, postId, userId);
return ResponseEntity.ok().build();
}

// 게시글 삭제
@DeleteMapping("/{postId}")
public void deletePost(
@PathVariable UUID postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.deletePost(postId, userId);
}

// 게시글 조회 (공지/일반)
@GetMapping
public ResponseEntity<Page<PostResponse>> getPosts(
@RequestParam BoardType boardType,
@RequestParam(defaultValue = "0") int pageNumber,
@RequestParam(defaultValue = "20") int pageSize) {
return ResponseEntity.ok(postService.getPosts(boardType, pageNumber, pageSize));
}

// 게시글 검색
@GetMapping("/search")
public ResponseEntity<Page<PostResponse>> searchPosts(
@RequestParam String keyword,
@RequestParam(defaultValue = "0") int pageNumber,
@RequestParam(defaultValue = "20") int pageSize) {
return ResponseEntity.ok(postService.searchPosts(keyword, pageNumber, pageSize));
}

// 게시물 상세 조회
@GetMapping("/{postId}")
public ResponseEntity<PostResponse> getPostDetail(
@PathVariable UUID postId,
@RequestParam(defaultValue = "0") int commentPageNumber,
@RequestParam(defaultValue = "20") int commentPageSize) {
PostResponse response = postService.getPostDetail(postId, commentPageNumber, commentPageSize);
return ResponseEntity.ok(response);
}

// 좋아요 토글
@PostMapping("/{postId}/like")
public ResponseEntity<Void> toggleLike(
@PathVariable UUID postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.toggleLike(postId, userId);
return ResponseEntity.ok().build();
}

// 북마크 토글
@PostMapping("/{postId}/bookmark")
public ResponseEntity<Void> toggleBookmark(
@PathVariable UUID postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.toggleBookmark(postId, userId);
return ResponseEntity.ok().build();
}

// 댓글 작성
@PostMapping("/{postId}/comment")
public ResponseEntity<Void> createComment(
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.createComment(request, userId);
return ResponseEntity.ok().build();
}

// 댓글 수정
@PutMapping("/comment/{commentId}")
public void updateComment(
@PathVariable UUID commentId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.updateComment(request, commentId, userId);
}

// 댓글 삭제
@DeleteMapping("/comment/{commentId}")
public void deleteComment(
@PathVariable UUID commentId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
UUID userId = customUserDetails.getUserId();
postService.deleteComment(commentId, userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sejongisc.backend.board.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class CommentRequest {

@NotNull
private UUID postId;

@NotBlank(message = "댓글 내용은 필수 항목입니다.")
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.sejongisc.backend.board.dto;

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

import java.time.LocalDateTime;
import java.util.UUID;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.sejongisc.backend.board.entity.Comment;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class CommentResponse {
private UUID commentId;
private UUID postId;
private String content;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;

public static CommentResponse of(Comment comment) {
return CommentResponse.builder()
.commentId(comment.getCommentId())
.postId(comment.getPostId())
.content(comment.getContent())
.createdDate(comment.getCreatedDate())
.updatedDate(comment.getUpdatedDate())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sejongisc.backend.board.dto;

import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.sejongisc.backend.board.entity.PostAttachment;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostAttachmentResponse {
private UUID postAttachmentId;
private String originalFilename;
private String filePath;

public static PostAttachmentResponse of(PostAttachment attachment) {
return PostAttachmentResponse.builder()
.postAttachmentId(attachment.getPostAttachmentId())
.originalFilename(attachment.getOriginalFilename())
.filePath(attachment.getFilePath())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.sejongisc.backend.board.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.sejongisc.backend.board.entity.BoardType;
import org.sejongisc.backend.board.entity.PostType;
import org.springframework.web.multipart.MultipartFile;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class PostRequest {

@NotNull(message = "게시판 타입을 선택해주세요.")
private BoardType boardType;

@NotBlank(message = "제목은 필수 항목입니다.")
private String title;

@NotBlank(message = "내용은 필수 항목입니다.")
private String content;

@NotNull(message = "게시글 타입을 선택해주세요.")
private PostType postType;

private List<MultipartFile> files;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sejongisc.backend.board.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.sejongisc.backend.board.entity.BoardType;
import org.sejongisc.backend.board.entity.Post;
import org.sejongisc.backend.board.entity.PostType;

import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
import org.sejongisc.backend.user.entity.User;
import org.springframework.data.domain.Page;

@ToString
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class PostResponse {

private UUID postId;
private BoardType boardType;
private User user;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

사용자 엔티티 직접 노출로 인한 보안/직렬화 위험 차단 필요
PostResponseUser 엔티티를 그대로 반환하면 민감 정보가 외부로 노출되고, LAZY 로딩 환경에서 직렬화 시 LazyInitializationException이 빈번히 발생합니다. API 전용 사용자 DTO 또는 최소한의 식별 정보만 노출하도록 구조를 바꿔주세요.

-  private User user;
+  private UUID authorId;
+  private String authorNickname;
+  // 필요 시 별도 UserResponse DTO 도입 고려
🤖 Prompt for AI Agents
backend/src/main/java/org/sejongisc/backend/board/dto/PostResponse.java around
line 29: 현재 PostResponse가 User 엔티티를 직접 보유하고 있어 민감정보가 노출되고 LAZY 로딩 시 직렬화 예외가 발생할
수 있으므로 User 엔티티를 제거하고 API 전용 최소 정보 DTO(예: UserSummaryDto 또는 UserIdNameDto)를 필드로
사용하도록 바꿉니다. 구체적으로 PostResponse에서 User 타입 필드를 삭제하고 id, username 등 필요한 식별/표시 필드만
담는 새로운 DTO 클래스를 만들거나 기존 DTO를 사용하며, 엔티티 -> DTO 매핑은 서비스 계층에서 수행해서 필요한 필드만 복사(또는
JPA 프로젝션/쿼리로 선택 조회)하도록 변경하고, 컨트롤러/매퍼 호출부도 이 DTO로 반환되게 수정하세요.

private String title;
private String content;
private PostType postType;
private Integer bookmarkCount;
private Integer likeCount;
private Integer commentCount;
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
private Page<CommentResponse> comments;
private List<PostAttachmentResponse> attachments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sejongisc.backend.board.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

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

@Getter
@AllArgsConstructor
public class PostSummaryResponse {
private UUID id;
private String title;
private int likeCount;
private int commentCount;
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sejongisc.backend.board.entity;

public enum BoardType {
GENERAL, // 전체 게시판
FINANCE_IT, // 금융 IT 게시판
ASSET_MANAGEMENT // 자산 운용 게시판
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sejongisc.backend.board.entity;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
import java.util.UUID;
import org.sejongisc.backend.common.entity.postgres.BasePostgresEntity;
import org.sejongisc.backend.user.entity.User;

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Comment extends BasePostgresEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID commentId;

@Column(nullable = false)
private UUID postId;

@ManyToOne(fetch = FetchType.LAZY)
private User user;

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