diff --git a/back/src/main/java/com/back/domain/file/service/VideoService.java b/back/src/main/java/com/back/domain/file/service/VideoService.java index df65b4c0..3885f771 100644 --- a/back/src/main/java/com/back/domain/file/service/VideoService.java +++ b/back/src/main/java/com/back/domain/file/service/VideoService.java @@ -17,6 +17,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.UUID; import java.util.stream.Collectors; @@ -35,13 +36,13 @@ public Video createVideo(String transcodingStatus, String originalPath, String o public Video getNewsByUuid(String uuid) { return videoRepository.findByUuid(uuid) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 비디오입니다.")); + .orElseThrow(() -> new NoSuchElementException("존재하지 않는 비디오입니다.")); } //HeadObjectRequest 고려 public URL generateUploadUrl(String bucket, String objectKey) { if(!isExist(bucket, objectKey)){ - throw new RuntimeException("요청한 파일이 존재하지 않습니다: " + objectKey); + throw new NoSuchElementException("요청한 파일이 존재하지 않습니다: " + objectKey); } PutObjectRequest request = PutObjectRequest.builder() @@ -64,7 +65,7 @@ public URL generateUploadUrl(String bucket, String objectKey) { public URL generateDownloadUrl(String bucket, String objectKey) { if(!isExist(bucket, objectKey)){ - throw new RuntimeException("요청한 파일이 존재하지 않습니다: " + objectKey); + throw new NoSuchElementException("요청한 파일이 존재하지 않습니다: " + objectKey); } GetObjectRequest request = GetObjectRequest.builder() diff --git a/back/src/main/java/com/back/domain/news/comment/controller/CommentController.java b/back/src/main/java/com/back/domain/news/comment/controller/CommentController.java index d89ec9cb..361b27fd 100644 --- a/back/src/main/java/com/back/domain/news/comment/controller/CommentController.java +++ b/back/src/main/java/com/back/domain/news/comment/controller/CommentController.java @@ -10,7 +10,9 @@ import com.back.domain.news.news.service.NewsService; import com.back.global.rq.Rq; import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -25,6 +27,7 @@ public class CommentController { private final Rq rq; @GetMapping + @Operation(summary = "댓글 목록 조회", description = "특정 뉴스의 댓글 목록을 불러옵니다.") public RsData> getComments(@PathVariable Long newsId) { News news = newsService.getNewsById(newsId); List comments = commentService.getComments(news); @@ -35,6 +38,7 @@ public RsData> getComments(@PathVariable Long newsId) { } @PostMapping + @Operation(summary = "댓글 생성", description = "특정 뉴스에 댓글을 생성합니다. 로그인한 사용자만 접근할 수 있습니다.") public RsData createComment(@PathVariable Long newsId, @RequestBody CommentCreateRequest request) { Member member = rq.getActor(); if (member == null) { @@ -47,25 +51,39 @@ public RsData createComment(@PathVariable Long newsId, @Request } @PutMapping("/{commentId}") + @Operation(summary = "댓글 수정", description = "특정 뉴스의 댓글을 수정합니다. 댓글 작성자만 접근할 수 있습니다.") public RsData updateComment(@PathVariable Long newsId, @PathVariable Long commentId, @RequestBody CommentUpdateRequest request) { Member member = rq.getActor(); if (member == null) { return new RsData<>("401", "로그인이 필요합니다."); } - News news = newsService.getNewsById(newsId); - Comment updatedComment = commentService.updateComment(member, news, commentId, request.content()); - CommentResponse commentResponse = new CommentResponse(updatedComment); - return new RsData<>("200", "댓글 수정 완료", commentResponse); + try { + News news = newsService.getNewsById(newsId); + Comment updatedComment = commentService.updateComment(member, news, commentId, request.content()); + CommentResponse commentResponse = new CommentResponse(updatedComment); + return new RsData<>("200", "댓글 수정 완료", commentResponse); + } catch (AccessDeniedException e) { + return new RsData<>("403", e.getMessage()); + } catch (IllegalArgumentException e) { + return new RsData<>("400", e.getMessage()); + } } @DeleteMapping("/{commentId}") + @Operation(summary = "댓글 삭제", description = "특정 뉴스의 댓글을 삭제합니다. 댓글 작성자만 접근할 수 있습니다.") public RsData deleteComment(@PathVariable Long newsId, @PathVariable Long commentId) { Member member = rq.getActor(); if (member == null) { return new RsData<>("401", "로그인이 필요합니다."); } - News news = newsService.getNewsById(newsId); - commentService.deleteComment(member, news, commentId); - return new RsData<>("200", "댓글 삭제 완료"); + try { + News news = newsService.getNewsById(newsId); + commentService.deleteComment(member, news, commentId); + return new RsData<>("200", "댓글 삭제 완료"); + } catch (AccessDeniedException e) { + return new RsData<>("403", e.getMessage()); + } catch (IllegalArgumentException e) { + return new RsData<>("400", e.getMessage()); + } } } diff --git a/back/src/main/java/com/back/domain/news/comment/service/CommentService.java b/back/src/main/java/com/back/domain/news/comment/service/CommentService.java index 9e30c9c3..8644cb49 100644 --- a/back/src/main/java/com/back/domain/news/comment/service/CommentService.java +++ b/back/src/main/java/com/back/domain/news/comment/service/CommentService.java @@ -5,11 +5,12 @@ import com.back.domain.news.comment.repository.CommentRepository; import com.back.domain.news.news.entity.News; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; +import java.util.NoSuchElementException; @Service @RequiredArgsConstructor @@ -26,14 +27,16 @@ public List getComments(News news) { return commentRepository.findByNews(news); } - public Optional getCommentById(Long commentId) { - return commentRepository.findById(commentId); + public Comment getCommentById(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new NoSuchElementException("Comment not found: " + commentId)); } public Comment updateComment(Member member, News news, Long commentId, String content) { - Comment comment = getCommentById(commentId).orElseThrow(() -> new IllegalArgumentException("Comment not found")); + Comment comment = getCommentById(commentId); + if (!comment.getMember().equals(member)) { - throw new IllegalArgumentException("You do not have permission to update this comment."); + throw new AccessDeniedException("You do not have permission to update this comment."); } if (!comment.getNews().equals(news)) { throw new IllegalArgumentException("This comment does not belong to the given news."); @@ -43,9 +46,10 @@ public Comment updateComment(Member member, News news, Long commentId, String co } public void deleteComment(Member member, News news, Long commentId) { - Comment comment = getCommentById(commentId).orElseThrow(() -> new IllegalArgumentException("Comment not found")); + Comment comment = getCommentById(commentId); + if (!comment.getMember().equals(member)) { - throw new IllegalArgumentException("You do not have permission to delete this comment."); + throw new AccessDeniedException("You do not have permission to delete this comment."); } if (!comment.getNews().equals(news)) { throw new IllegalArgumentException("This comment does not belong to the given news."); diff --git a/back/src/main/java/com/back/domain/news/news/controller/NewsController.java b/back/src/main/java/com/back/domain/news/news/controller/NewsController.java index fa9679c0..cb86871d 100644 --- a/back/src/main/java/com/back/domain/news/news/controller/NewsController.java +++ b/back/src/main/java/com/back/domain/news/news/controller/NewsController.java @@ -9,6 +9,7 @@ import com.back.domain.news.news.service.NewsService; import com.back.global.rq.Rq; import com.back.global.rsData.RsData; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; @@ -25,8 +26,15 @@ public class NewsController { private final Rq rq; @PostMapping + @Operation(summary = "뉴스 생성", description = "뉴스를 생성합니다. ADMIN 사용자만 접근할 수 있습니다.") public RsData createNews(@RequestBody NewsCreateRequest request) { - Member member = rq.getActor(); + Member member = rq.getActorFromDb().get(); + if (member == null) { + return new RsData<>("401", "로그인 후 이용해주세요.", null); + } + if (member.getRole() != Member.Role.ADMIN) { + return new RsData<>("403", "권한이 없습니다.", null); + } Video video = videoService.getNewsByUuid(request.videoUuid()); News news = newsService.createNews(member, request.title(), video, request.content()); NewsCreateResponse response = new NewsCreateResponse(news.getTitle(), news.getVideo().getUuid(), news.getContent(), member.getName()); @@ -34,13 +42,15 @@ public RsData createNews(@RequestBody NewsCreateRequest requ } @GetMapping("{newsId}") - public RsData getNews(@PathVariable Long newsId) { + @Operation(summary = "뉴스 단건 조회", description = "특정 ID의 뉴스를 읽어옵니다.") + public RsData getNews(@PathVariable("newsId") Long newsId) { News news = newsService.getNewsById(newsId); NewsGetResponse response = new NewsGetResponse(news); return new RsData<>("200", "뉴스 읽어오기 완료", response); } @GetMapping + @Operation(summary = "뉴스 목록 조회", description = "뉴스 목록을 페이지 단위로 불러옵니다. 기본 페이지 크기는 10입니다.") public RsData> getNewsList( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { @@ -55,8 +65,12 @@ public RsData> getNewsList( } @PutMapping("{newsId}/likes") - public RsData likeNews(@PathVariable Long newsId) { - Member member = rq.getActor(); + @Operation(summary = "뉴스 좋아요", description = "특정 ID의 뉴스를 좋아요 합니다. 로그인한 사용자만 접근할 수 있습니다.") + public RsData likeNews(@PathVariable("newsId") Long newsId) { + Member member = rq.getActorFromDb().get(); + if (member == null) { + return new RsData<>("401", "로그인 후 이용해주세요.", null); + } try { likeService.likeNews(member, newsId); NewsLikeResponse response = new NewsLikeResponse(member.getId(), newsId, likeService.getLikeCount(newsId)); @@ -67,10 +81,17 @@ public RsData likeNews(@PathVariable Long newsId) { } @PutMapping("{newsId}") - public RsData modifyNews(@RequestBody NewsUpdateRequest request) { - Member member = rq.getActor(); + @Operation(summary = "뉴스 수정", description = "특정 ID의 뉴스를 수정합니다. ADMIN 사용자만 접근할 수 있습니다.") + public RsData modifyNews(@PathVariable("newsId") Long newsId, @RequestBody NewsUpdateRequest request) { + Member member = rq.getActorFromDb().get(); + if (member == null) { + return new RsData<>("401", "로그인 후 이용해주세요.", null); + } + if (member.getRole() != Member.Role.ADMIN) { + return new RsData<>("403", "권한이 없습니다.", null); + } try { - News news = newsService.getNewsById(request.newsId()); + News news = newsService.getNewsById(newsId); Video video = videoService.getNewsByUuid(request.videoUuid()); News updatedNews = newsService.updateNews(member, news, request.title(), video, request.content()); NewsUpdateResponse response = new NewsUpdateResponse(updatedNews); @@ -81,10 +102,17 @@ public RsData modifyNews(@RequestBody NewsUpdateRequest requ } @DeleteMapping("{newsId}") - public RsData deleteNews(@PathVariable Long newsId) { - Member member = rq.getActor(); + @Operation(summary = "뉴스 삭제", description = "특정 ID의 뉴스를 삭제합니다. ADMIN 사용자만 접근할 수 있습니다.") + public RsData deleteNews(@PathVariable("newsId") Long newsId) { + Member member = rq.getActorFromDb().get(); + if (member == null){ + return new RsData<>("401", "로그인 후 이용해주세요.", null); + } + if (member.getRole() != Member.Role.ADMIN) { + return new RsData<>("403", "권한이 없습니다.", null); + } News news = newsService.getNewsById(newsId); newsService.deleteNews(member, news); - return new RsData<>("200", news.getId()+"번 뉴스가 삭제되었습니다.", null); + return new RsData<>("200", newsId + "번 뉴스가 삭제되었습니다.", null); } } diff --git a/back/src/main/java/com/back/domain/news/news/dto/NewsUpdateRequest.java b/back/src/main/java/com/back/domain/news/news/dto/NewsUpdateRequest.java index 4b82536c..bc531918 100644 --- a/back/src/main/java/com/back/domain/news/news/dto/NewsUpdateRequest.java +++ b/back/src/main/java/com/back/domain/news/news/dto/NewsUpdateRequest.java @@ -1,7 +1,6 @@ package com.back.domain.news.news.dto; public record NewsUpdateRequest( - Long newsId, String title, String content, String videoUuid diff --git a/back/src/main/java/com/back/domain/news/news/entity/News.java b/back/src/main/java/com/back/domain/news/news/entity/News.java index 23c8c02f..d4fc8735 100644 --- a/back/src/main/java/com/back/domain/news/news/entity/News.java +++ b/back/src/main/java/com/back/domain/news/news/entity/News.java @@ -18,7 +18,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class News extends BaseEntity { - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne private Member member; private String title; @OneToOne diff --git a/back/src/main/java/com/back/domain/news/news/service/NewsService.java b/back/src/main/java/com/back/domain/news/news/service/NewsService.java index 837ccedf..36222a78 100644 --- a/back/src/main/java/com/back/domain/news/news/service/NewsService.java +++ b/back/src/main/java/com/back/domain/news/news/service/NewsService.java @@ -12,6 +12,8 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import java.util.NoSuchElementException; + @Service @RequiredArgsConstructor public class NewsService { @@ -28,7 +30,7 @@ public Page getNewsByPage(int page, int size) { } public News getNewsById(Long id) { - return newsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("뉴스를 찾을 수 없습니다.")); + return newsRepository.findById(id).orElseThrow(() -> new NoSuchElementException("뉴스를 찾을 수 없습니다.")); } public News updateNews(Member member, News news, String title, Video video, String content) {