diff --git a/src/main/java/com/example/FixLog/config/WebConfig.java b/src/main/java/com/example/FixLog/config/WebConfig.java index 00b5a09..3da71a9 100644 --- a/src/main/java/com/example/FixLog/config/WebConfig.java +++ b/src/main/java/com/example/FixLog/config/WebConfig.java @@ -10,7 +10,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 모든 경로에 대해 - .allowedOriginPatterns("*") // 모든 도메인 허용 (개발용) → 배포 시 정확한 프론트 주소로 수정 권장 + .allowedOriginPatterns("https://fixlog.netlify.app", "http://localhost:3000") // 모든 도메인 허용 (개발용) → 배포 시 정확한 프론트 주소로 수정 권장 .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); // 인증정보(쿠키, Authorization) 허용 diff --git a/src/main/java/com/example/FixLog/controller/PostController.java b/src/main/java/com/example/FixLog/controller/PostController.java index 7f3dfaf..9497952 100644 --- a/src/main/java/com/example/FixLog/controller/PostController.java +++ b/src/main/java/com/example/FixLog/controller/PostController.java @@ -1,5 +1,6 @@ package com.example.FixLog.controller; +import com.example.FixLog.dto.post.NewPostRequestDto; import com.example.FixLog.dto.post.PostRequestDto; import com.example.FixLog.dto.Response; import com.example.FixLog.dto.post.PostResponseDto; @@ -16,30 +17,43 @@ public PostController(PostService postService){ this.postService = postService; } + // 게시글 작성하기 @PostMapping public Response createPost(@RequestBody PostRequestDto postRequestDto){ postService.createPost(postRequestDto); return Response.success("게시글 작성 성공.", null); } + // 이미지 마크다운 형식으로 변환하기 @PostMapping("/images") public Response uploadImage(@RequestPart("imageFile") MultipartFile imageFile){ String markdownImage = postService.uploadImage(imageFile); return Response.success("이미지 마크다운 형식으로 변환", markdownImage); } + // 게시글 수정하기 + @PatchMapping("/{postId}/edit") + public Response editPost(@PathVariable("postId") Long postId, + @RequestBody NewPostRequestDto newPostRequestDto){ + postService.editPost(postId, newPostRequestDto); + return Response.success("게시글 수정 성공.", null); + } + + // 게시글 보기 @GetMapping("/{postId}") public Response viewPost(@PathVariable("postId") Long postId){ PostResponseDto viewPost = postService.viewPost(postId); return Response.success("게시글 조회하기 성공", viewPost); } + // 좋아요 누르기/취소하기 @PostMapping("/{postId}/like") public Response togglePostLike(@PathVariable("postId") Long postId){ String message = postService.togglePostLike(postId); return Response.success(message, null); // 좋아요 수정하기 } + // 북마크 누르기/취소하기 @PostMapping("/{postId}/bookmark") public Response toggleBookmark(@PathVariable("postId") Long postId) { String message = postService.toggleBookmark(postId); diff --git a/src/main/java/com/example/FixLog/domain/post/Post.java b/src/main/java/com/example/FixLog/domain/post/Post.java index bdb0ac2..e0db596 100644 --- a/src/main/java/com/example/FixLog/domain/post/Post.java +++ b/src/main/java/com/example/FixLog/domain/post/Post.java @@ -79,4 +79,41 @@ public class Post { @OneToMany(mappedBy = "postId", cascade = CascadeType.ALL, orphanRemoval = true) private List postLikes = new ArrayList<>(); + public void clearTags(){ + postTags.clear(); + } + + public void changeTitle(String newTitle){ + this.postTitle = newTitle; + } + public void changeCoverImage(String newCoverImage){ + this.coverImage=newCoverImage; + } + public void changeProblem(String newProblem){ + this.problem = newProblem; + } + public void changeErrorMessage(String newErrorMessage){ + this.errorMessage = newErrorMessage; + } + public void changeEnvironment(String newEnvironment){ + this.environment = newEnvironment; + } + public void changeReproduceCode(String newReproduceCode){ + this.reproduceCode = newReproduceCode; + } + public void changeSolutionCode(String newSolutionCode){ + this.solutionCode = newSolutionCode; + } + public void changeCauseAnalysis(String newCauseAnalysis){ + this.causeAnalysis = newCauseAnalysis; + } + public void changeReferenceLink(String newReferenceLink){ + this.referenceLink = newReferenceLink; + } + public void changeExtraContent(String newExtraContent){ + this.extraContent = newExtraContent; + } + public void updateEditedAt(LocalDateTime newLocalDateTime){ + this.editedAt = newLocalDateTime; + } } \ No newline at end of file diff --git a/src/main/java/com/example/FixLog/dto/post/NewPostRequestDto.java b/src/main/java/com/example/FixLog/dto/post/NewPostRequestDto.java new file mode 100644 index 0000000..34c4696 --- /dev/null +++ b/src/main/java/com/example/FixLog/dto/post/NewPostRequestDto.java @@ -0,0 +1,21 @@ +package com.example.FixLog.dto.post; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class NewPostRequestDto { + private String postTitle; + private String coverImageUrl; + private String problem; + private String errorMessage; + private String environment; + private String reproduceCode; + private String solutionCode; + private String causeAnalysis; + private String referenceLink; + private String extraContent; + + private List tags; +} diff --git a/src/main/java/com/example/FixLog/exception/ErrorCode.java b/src/main/java/com/example/FixLog/exception/ErrorCode.java index 7e059da..db86c04 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -30,9 +30,9 @@ public enum ErrorCode { UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "권한이 없습니다."), INVALID_REQUEST(HttpStatus.BAD_REQUEST, "요청 데이터가 유효하지 않습니다."), S3_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "S3 파일 업로드에 실패했습니다."), - IMAGE_UPLOAD_FAILED(HttpStatus.NOT_FOUND, "이미지 파일이 업로드되지 않았습니다."), - LOGOUT_SUCCESS(HttpStatus.OK, "로그아웃이 정상적으로 처리되었습니다."); + IMAGE_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "이미지 파일이 업로드되지 않았습니다."), + NO_CONTENT_CHANGED(HttpStatus.BAD_REQUEST, "변경된 내용이 없습니다."); private final HttpStatus status; private final String message; -} \ No newline at end of file +} diff --git a/src/main/java/com/example/FixLog/service/PostService.java b/src/main/java/com/example/FixLog/service/PostService.java index da8b4cc..6970fe5 100644 --- a/src/main/java/com/example/FixLog/service/PostService.java +++ b/src/main/java/com/example/FixLog/service/PostService.java @@ -8,6 +8,7 @@ import com.example.FixLog.domain.post.PostTag; import com.example.FixLog.domain.tag.Tag; import com.example.FixLog.domain.tag.TagCategory; +import com.example.FixLog.dto.post.NewPostRequestDto; import com.example.FixLog.dto.post.PostDto; import com.example.FixLog.dto.post.PostRequestDto; import com.example.FixLog.dto.post.PostResponseDto; @@ -62,7 +63,7 @@ public String getDefaultImage(String image){ return imageUrl; } - // 게시글 생성하기 + // 게시글 작성하기 @Transactional public void createPost(PostRequestDto postRequestDto){ Member member = memberService.getCurrentMemberInfo(); @@ -99,18 +100,6 @@ public void createPost(PostRequestDto postRequestDto){ postRepository.save(newPost); } - // 이미지 파일 마크다운으로 변경 - public String uploadImage(MultipartFile imageFile){ - SecurityContextHolder.getContext().getAuthentication(); - - if (imageFile == null || imageFile.isEmpty()){ - throw new CustomException(ErrorCode.IMAGE_UPLOAD_FAILED); - } - - String imageUrl = s3Service.upload(imageFile, "post-image"); - return "![image](" + imageUrl + ")"; - } - // 태그 다 선택 했는지 private List fetchAndValidateTags(List tagIds){ // 태그 ID로 Tag 엔티티 조회 @@ -160,6 +149,78 @@ private void validatePost(PostRequestDto postRequestDto){ | postRequestDto.getReproduceCode().isBlank() | postRequestDto.getSolutionCode().isBlank()) throw new CustomException(ErrorCode.REQUIRED_CONTENT_MISSING); } + private void validatePost(NewPostRequestDto newPostRequestDto){ + if (newPostRequestDto.getPostTitle().isBlank() | newPostRequestDto.getProblem().isBlank() + | newPostRequestDto.getErrorMessage().isBlank() | newPostRequestDto.getEnvironment().isBlank() + | newPostRequestDto.getReproduceCode().isBlank() | newPostRequestDto.getSolutionCode().isBlank()) + throw new CustomException(ErrorCode.REQUIRED_CONTENT_MISSING); + } + + // 이미지 파일 마크다운으로 변경 + @Transactional + public String uploadImage(MultipartFile imageFile){ + SecurityContextHolder.getContext().getAuthentication(); + + if (imageFile == null || imageFile.isEmpty()){ + throw new CustomException(ErrorCode.IMAGE_UPLOAD_FAILED); + } + + String imageUrl = s3Service.upload(imageFile, "post-image"); + return "![image](" + imageUrl + ")"; + } + + @Transactional + public void editPost(Long postId, NewPostRequestDto newPostRequestDto) { + Member member = memberService.getCurrentMemberInfo(); + Post post = postRepository.findById(postId) + .orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND)); + + // 북마크 카테고리별로 선택 제한 두기 + List tags = fetchAndValidateTags(newPostRequestDto.getTags()); + + // 아무것도 변경이 없으면 예외처리 + if (Objects.equals(post.getPostTitle(), newPostRequestDto.getPostTitle()) + & Objects.equals(post.getCoverImage(), newPostRequestDto.getCoverImageUrl()) + & Objects.equals(post.getProblem(), newPostRequestDto.getProblem()) + & Objects.equals(post.getErrorMessage(), newPostRequestDto.getErrorMessage()) + & Objects.equals(post.getEnvironment(), newPostRequestDto.getEnvironment()) + & Objects.equals(post.getReproduceCode(), newPostRequestDto.getReproduceCode()) + & Objects.equals(post.getSolutionCode(), newPostRequestDto.getSolutionCode()) + & Objects.equals(post.getCauseAnalysis(), newPostRequestDto.getCauseAnalysis()) + & Objects.equals(post.getReferenceLink(), newPostRequestDto.getReferenceLink()) + & Objects.equals(post.getExtraContent(), newPostRequestDto.getExtraContent()) + & compareTags(post.getPostTags(), tags)){ + throw new CustomException(ErrorCode.NO_CONTENT_CHANGED); + } + + // 필드 업데이트 + post.changeTitle(newPostRequestDto.getPostTitle()); + post.changeCoverImage(newPostRequestDto.getCoverImageUrl()); + post.changeProblem(newPostRequestDto.getProblem()); + post.changeErrorMessage(newPostRequestDto.getErrorMessage()); + post.changeEnvironment(newPostRequestDto.getEnvironment()); + post.changeReproduceCode(newPostRequestDto.getReproduceCode()); + post.changeSolutionCode(newPostRequestDto.getSolutionCode()); + post.changeCauseAnalysis(newPostRequestDto.getCauseAnalysis()); + post.changeReferenceLink(newPostRequestDto.getReferenceLink()); + post.changeExtraContent(newPostRequestDto.getExtraContent()); + post.updateEditedAt(LocalDateTime.now()); + + // 태그 저장 + post.clearTags(); // 기존 태그 다 제거 + for (Tag tag : tags) { + PostTag postTag = new PostTag(post, tag); + post.getPostTags().add(postTag); + } + } + + private boolean compareTags(List currentPostTags, List newTags) { + List currentTags = currentPostTags.stream() + .map(PostTag::getTagId) + .toList(); + + return new HashSet<>(currentTags).equals(new HashSet<>(newTags)); + } // 게시글 조회하기 public PostResponseDto viewPost(Long postId){