From 82c5f55b690aa04976f20e75220a47df1e15ca4a Mon Sep 17 00:00:00 2001 From: youngseo22 Date: Sun, 22 Jun 2025 18:40:37 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix(tag)=20:=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=ED=86=A0=ED=81=B0=20=EC=97=86=EC=96=B4?= =?UTF-8?q?=EB=8F=84=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/FixLog/config/SecurityConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/example/FixLog/config/SecurityConfig.java b/src/main/java/com/example/FixLog/config/SecurityConfig.java index ede3958..73bbcba 100644 --- a/src/main/java/com/example/FixLog/config/SecurityConfig.java +++ b/src/main/java/com/example/FixLog/config/SecurityConfig.java @@ -37,6 +37,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.GET, "/members/check-nickname").permitAll() .requestMatchers(HttpMethod.GET, "/", "/main", "/main/**").permitAll() .requestMatchers(HttpMethod.GET, "/posts/**").permitAll() + .requestMatchers(HttpMethod.GET, "/tags").permitAll() // h2-console (로컬 테스트용) .requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll() // 배포 확인용 임시 허용 From cc66255d84858cf2153aa9a084e60e0eaab45381 Mon Sep 17 00:00:00 2001 From: youngseo22 <135317722+youngseo22@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:00:38 +0900 Subject: [PATCH 2/5] Update ErrorCode.java --- src/main/java/com/example/FixLog/exception/ErrorCode.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/FixLog/exception/ErrorCode.java b/src/main/java/com/example/FixLog/exception/ErrorCode.java index 7e059da..a31a4ce 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -30,9 +30,8 @@ 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.NOT_FOUND, "이미지 파일이 업로드되지 않았습니다."); private final HttpStatus status; private final String message; -} \ No newline at end of file +} From 3289b544cc26ffbd90c861cad60e5cdc279f5b51 Mon Sep 17 00:00:00 2001 From: chae_1 <129128205+sungchaewon@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:09:13 +0900 Subject: [PATCH 3/5] Update WebConfig.java --- src/main/java/com/example/FixLog/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/FixLog/config/WebConfig.java b/src/main/java/com/example/FixLog/config/WebConfig.java index 00b5a09..0094e4b 100644 --- a/src/main/java/com/example/FixLog/config/WebConfig.java +++ b/src/main/java/com/example/FixLog/config/WebConfig.java @@ -9,7 +9,7 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") // 모든 경로에 대해 + registry.addMapping("https://fixlog.netlify.app", "http://localhost:3000") // 모든 경로에 대해 .allowedOriginPatterns("*") // 모든 도메인 허용 (개발용) → 배포 시 정확한 프론트 주소로 수정 권장 .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("*") From ace983ac5bc74498618f7e8cb598f5afe85b3590 Mon Sep 17 00:00:00 2001 From: chae_1 <129128205+sungchaewon@users.noreply.github.com> Date: Sun, 22 Jun 2025 19:11:02 +0900 Subject: [PATCH 4/5] Update WebConfig.java --- src/main/java/com/example/FixLog/config/WebConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/FixLog/config/WebConfig.java b/src/main/java/com/example/FixLog/config/WebConfig.java index 0094e4b..3da71a9 100644 --- a/src/main/java/com/example/FixLog/config/WebConfig.java +++ b/src/main/java/com/example/FixLog/config/WebConfig.java @@ -9,8 +9,8 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("https://fixlog.netlify.app", "http://localhost:3000") // 모든 경로에 대해 - .allowedOriginPatterns("*") // 모든 도메인 허용 (개발용) → 배포 시 정확한 프론트 주소로 수정 권장 + registry.addMapping("/**") // 모든 경로에 대해 + .allowedOriginPatterns("https://fixlog.netlify.app", "http://localhost:3000") // 모든 도메인 허용 (개발용) → 배포 시 정확한 프론트 주소로 수정 권장 .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); // 인증정보(쿠키, Authorization) 허용 From fa574df27f6f965e73900f5fa2166b3b96504837 Mon Sep 17 00:00:00 2001 From: youngseo22 Date: Sun, 22 Jun 2025 19:36:37 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat(edit=20post)=20:=20#60=20-=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=88=98=EC=A0=95=ED=95=98=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FixLog/controller/PostController.java | 14 +++ .../com/example/FixLog/domain/post/Post.java | 37 ++++++++ .../FixLog/dto/post/NewPostRequestDto.java | 21 +++++ .../example/FixLog/exception/ErrorCode.java | 3 +- .../example/FixLog/service/PostService.java | 87 ++++++++++++++++--- 5 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/example/FixLog/dto/post/NewPostRequestDto.java 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 a31a4ce..db86c04 100644 --- a/src/main/java/com/example/FixLog/exception/ErrorCode.java +++ b/src/main/java/com/example/FixLog/exception/ErrorCode.java @@ -30,7 +30,8 @@ public enum ErrorCode { UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "권한이 없습니다."), INVALID_REQUEST(HttpStatus.BAD_REQUEST, "요청 데이터가 유효하지 않습니다."), S3_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "S3 파일 업로드에 실패했습니다."), - IMAGE_UPLOAD_FAILED(HttpStatus.NOT_FOUND, "이미지 파일이 업로드되지 않았습니다."); + IMAGE_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "이미지 파일이 업로드되지 않았습니다."), + NO_CONTENT_CHANGED(HttpStatus.BAD_REQUEST, "변경된 내용이 없습니다."); private final HttpStatus status; private final String message; 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){