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
@@ -0,0 +1,102 @@
package com.project.InsightPrep.domain.post.controller;

import com.project.InsightPrep.domain.post.controller.docs.PostControllerDocs;
import com.project.InsightPrep.domain.post.dto.CommentRequest;
import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentListItem;
import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentRes;
import com.project.InsightPrep.domain.post.dto.PostRequest;
import com.project.InsightPrep.domain.post.dto.PostResponse;
import com.project.InsightPrep.domain.post.dto.PostResponse.Created;
import com.project.InsightPrep.domain.post.dto.PostResponse.PostDetailDto;
import com.project.InsightPrep.domain.post.dto.PostResponse.PostListItemDto;
import com.project.InsightPrep.domain.post.service.CommentService;
import com.project.InsightPrep.domain.post.service.SharedPostService;
import com.project.InsightPrep.domain.question.dto.response.PageResponse;
import com.project.InsightPrep.global.common.response.ApiResponse;
import com.project.InsightPrep.global.common.response.code.ApiSuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/post")
@RequiredArgsConstructor
public class PostController implements PostControllerDocs {

private final SharedPostService sharedPostService;
private final CommentService commentService;

@PostMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<Created>> create(@RequestBody @Valid PostRequest.Create req) {
Long postId = sharedPostService.createPost(req);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.CREATE_POST_SUCCESS, new PostResponse.Created(postId)));
}

@GetMapping("/{postId}")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<PostDetailDto>> getPost(@PathVariable long postId) {
PostDetailDto dto = sharedPostService.getPostDetail(postId);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.GET_POST_SUCCESS, dto));
}

@PatchMapping("/{postId}/resolve")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<Void>> resolve(@PathVariable long postId) {
sharedPostService.resolve(postId);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.UPDATE_POST_STATUS_SUCCESS));
}

@GetMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<PageResponse<PostListItemDto>>> list(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
) {
PageResponse<PostListItemDto> body = sharedPostService.getPosts(page, size);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.GET_POSTS_SUCCESS, body));
}

@PostMapping("/{postId}/comments")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<CommentRes>> createComment(@PathVariable long postId, @RequestBody @Valid CommentRequest.CreateDto req) {
CommentRes res = commentService.createComment(postId, req);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.CREATE_COMMENT_SUCCESS, res));
}

@PutMapping("/{postId}/comments/{commentId}")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<Void>> updateComment(@PathVariable long postId, @PathVariable long commentId, @RequestBody @Valid CommentRequest.UpdateDto req) {
commentService.updateComment(postId, commentId, req);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.UPDATE_COMMENT_SUCCESS));
}

@DeleteMapping("/{postId}/comments/{commentId}")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<Void>> deleteComment(@PathVariable long postId, @PathVariable long commentId) {
commentService.deleteComment(postId, commentId);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.DELETE_COMMENT_SUCCESS));
}

@GetMapping("/{postId}/comments")
@PreAuthorize("hasRole('USER')")
public ResponseEntity<ApiResponse<PageResponse<CommentListItem>>> listComments(
@PathVariable long postId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
) {
PageResponse<CommentListItem> res = commentService.getComments(postId, page, size);
return ResponseEntity.ok(ApiResponse.of(ApiSuccessCode.GET_COMMENTS_SUCCESS, res));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.project.InsightPrep.domain.post.controller.docs;

import com.project.InsightPrep.domain.post.dto.CommentRequest;
import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentListItem;
import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentRes;
import com.project.InsightPrep.domain.post.dto.PostRequest;
import com.project.InsightPrep.domain.post.dto.PostResponse.Created;
import com.project.InsightPrep.domain.post.dto.PostResponse.PostDetailDto;
import com.project.InsightPrep.domain.post.dto.PostResponse.PostListItemDto;
import com.project.InsightPrep.domain.question.dto.response.PageResponse;
import com.project.InsightPrep.global.common.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@Tag(name = "Discussions", description = "Discussions 관련 API")
public interface PostControllerDocs {

@Operation(summary = "토론 글 등록", description = "특정 질문에 대하여 글을 등록합니다.")
public ResponseEntity<ApiResponse<Created>> create(@RequestBody @Valid PostRequest.Create req);

@Operation(summary = "토론 글 조회", description = "글을 조회합니다.")
public ResponseEntity<ApiResponse<PostDetailDto>> getPost(@PathVariable long postId);

@Operation(summary = "본인 글 상태 변경", description = "본인이 작성한 글의 상태를 해결 완료 상태로 변경합니다.")
public ResponseEntity<ApiResponse<Void>> resolve(@PathVariable long postId);

@Operation(summary = "글 리스트 조회", description = "사용자들이 작성한 글들을 조회합니다.")
public ResponseEntity<ApiResponse<PageResponse<PostListItemDto>>> list(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
);

@Operation(summary = "댓글 작성", description = "특정 글에 댓글을 작성합니다.")
public ResponseEntity<ApiResponse<CommentRes>> createComment(@PathVariable long postId, @RequestBody @Valid CommentRequest.CreateDto req);

@Operation(summary = "댓글 수정", description = "본인의 댓글을 수정합니다.")
public ResponseEntity<ApiResponse<Void>> updateComment(@PathVariable long postId, @PathVariable long commentId, @RequestBody @Valid CommentRequest.UpdateDto req);

@Operation(summary = "댓글 삭제", description = "본인의 댓글을 삭제합니다.")
public ResponseEntity<ApiResponse<Void>> deleteComment(@PathVariable long postId, @PathVariable long commentId);

@Operation(summary = "댓글 목록 조회", description = "특정 글에 작성된 댓글들을 조회합니다.")
public ResponseEntity<ApiResponse<PageResponse<CommentListItem>>> listComments(
@PathVariable long postId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.project.InsightPrep.domain.post.dto;

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

public class CommentRequest {

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CreateDto {
@NotBlank(message = "댓글 내용을 입력해주세요.")
private String content;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class UpdateDto {
@NotBlank(message = "수정할 내용을 입력해주세요.")
private String content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.project.InsightPrep.domain.post.dto;

import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class CommentResponse {

@Getter
@Builder
public static class CommentRes {
private long commentId;
private String content;
private long authorId;
private String authorNickname;
private long postId;
private LocalDateTime createdAt;
}

@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class CommentRow {
private long id;
private long postId;
private long memberId;
private String content;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CommentListItem {
private Long commentId;
private Long authorId;
private String authorNickname;
private String content;
private LocalDateTime createdAt;
private boolean mine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.project.InsightPrep.domain.post.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class PostRequest {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Create {
@NotNull
private Long answerId;

@NotBlank
@Size(max = 200)
private String title;

@NotBlank
private String content;
}

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class PostOwnerStatusDto {
private Long memberId;
private String status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.project.InsightPrep.domain.post.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

public class PostResponse {

@Getter
@AllArgsConstructor
public static class Created {
private Long postId;
}

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class PostDetailDto {
private Long postId;
private String title;
private String content;
private String status; // OPEN / RESOLVED
private LocalDateTime createdAt;

private Long authorId;
private String authorNickname;

private Long questionId;
private String category;
private String question;

private Long answerId;
private String answer;

private Long feedbackId;
private Integer score;
private String improvement;
private String modelAnswer;

private Boolean myPost;
private Long commentCount;
}

@Getter
@Setter
@Builder
public static class PostListItemDto {
private long postId;
private String title;
private LocalDateTime createdAt;
private String status;
private String question;
private String category;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.project.InsightPrep.domain.post.exception;

import com.project.InsightPrep.global.common.response.code.BaseErrorCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum PostErrorCode implements BaseErrorCode {

FORBIDDEN_OR_NOT_FOUND_ANSWER("FORBIDDEN_OR_NOT_FOUND_ANSWER", HttpStatus.BAD_REQUEST, "본인 답변이 아니거나 존재하지 않습니다."),
CREATE_FAILED("CREATE_FAILED", HttpStatus.BAD_REQUEST, "게시글 생성에 실패했습니다."),
POST_NOT_FOUND("POST_NOT_FOUND", HttpStatus.NOT_FOUND, "게시글을 찾을 수 없습니다."),
FORBIDDEN("FORBIDDEN", HttpStatus.FORBIDDEN, "본인만 변경이 가능합니다."),
ALREADY_RESOLVED("ALREADY_RESOLVED", HttpStatus.BAD_REQUEST, "이미 해결한 글입니다."),
CONFLICT("CONFLICT", HttpStatus.CONFLICT, "수정에 실패했습니다."),

COMMENT_NOT_FOUND("COMMENT_NOT_FOUND", HttpStatus.NOT_FOUND, "댓글을 찾을 수 없습니다."),
COMMENT_FORBIDDEN("COMMENT_FORBIDDEN", HttpStatus.FORBIDDEN, "댓글에 대한 권한이 없습니다.");

private final String code;
private final HttpStatus status;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.project.InsightPrep.domain.post.exception;

import com.project.InsightPrep.global.common.response.code.BaseErrorCode;
import com.project.InsightPrep.global.exception.CustomException;
import lombok.Getter;

@Getter
public class PostException extends CustomException {

public PostException(BaseErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.project.InsightPrep.domain.post.mapper;

import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentListItem;
import com.project.InsightPrep.domain.post.dto.CommentResponse.CommentRow;
import com.project.InsightPrep.domain.post.entity.Comment;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface CommentMapper {

void insertComment(Comment comment);

Comment findById(@Param("id") long id);

int updateContent(@Param("id") long id, @Param("memberId") long memberId, @Param("content") String content);

int deleteByIdAndMember(@Param("id") long id, @Param("memberId") long memberId);

CommentRow findRowById(@Param("id") long id);

List<CommentListItem> findByPostPaged(@Param("postId") long postId, @Param("limit") int limit, @Param("offset") int offset);

long countByPost(@Param("postId") long postId);
}
Loading
Loading