diff --git a/build.gradle b/build.gradle index 06d6dab..b4d868f 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // AWS SDK for S3 + implementation 'software.amazon.awssdk:s3:2.20.91' + // Query dsl 설정 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" diff --git a/src/main/java/com/nemo/dong_geul_be/board/controller/BoardDGController.java b/src/main/java/com/nemo/dong_geul_be/board/controller/BoardDGController.java index c272238..5d8994b 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/controller/BoardDGController.java +++ b/src/main/java/com/nemo/dong_geul_be/board/controller/BoardDGController.java @@ -1,25 +1,40 @@ package com.nemo.dong_geul_be.board.controller; -import com.nemo.dong_geul_be.board.domain.dto.request.CreatePostRequest; +import com.nemo.dong_geul_be.authentication.util.SecurityContextUtil; +import com.nemo.dong_geul_be.board.domain.dto.request.CreateCommentRequest; +import com.nemo.dong_geul_be.board.domain.dto.request.DonggeulPostRequest; +import com.nemo.dong_geul_be.board.domain.dto.response.CommentResponse; import com.nemo.dong_geul_be.board.domain.dto.response.PostDTO; -import com.nemo.dong_geul_be.board.domain.dto.response.PostDetailResponse; +import com.nemo.dong_geul_be.board.domain.dto.response.PostDetailIMGResponse; import com.nemo.dong_geul_be.board.domain.entity.Comment; import com.nemo.dong_geul_be.board.domain.entity.Post; +import com.nemo.dong_geul_be.board.repository.CommentRepository; +import com.nemo.dong_geul_be.board.repository.PostRepository; import com.nemo.dong_geul_be.board.service.CommentService; import com.nemo.dong_geul_be.board.service.PostService; import com.nemo.dong_geul_be.global.response.Response; +import com.nemo.dong_geul_be.member.domain.entity.Member; +import com.nemo.dong_geul_be.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @RestController @RequestMapping("/api") @RequiredArgsConstructor -public class BoardDGController { //동글동글 : 동아리 홍보 +public class BoardDGController { // 동글동글 : 동아리 홍보 private final PostService postService; private final CommentService commentService; + private final PostRepository postRepository; + private final CommentRepository commentRepository; + private final SecurityContextUtil securityContextUtil; // SecurityContextUtil 추가 + private final MemberRepository memberRepository; // 메인페이지 @GetMapping("/donggeul") @@ -44,26 +59,113 @@ public Response> getPostFalseExternalTruePosts() { // 게시글 상세보기 @GetMapping("/donggeul/{postId}") - public Response getPostDetails(@PathVariable Long postId) { + public Response getPostDetails(@PathVariable Long postId) { + Post post = postService.getPostById(postId); + + List imageUrls = postService.getImageUrlsByPostId(postId); List comments = commentService.getCommentsByPost(postId); - PostDetailResponse response = new PostDetailResponse(post, comments); + PostDetailIMGResponse.SimplePostDTO simplePostDTO = new PostDetailIMGResponse.SimplePostDTO( + post.getId(), + post.getTitle(), + post.getContent(), + post.getHashtag(), + post.getPostType(), + post.getCreatedAt().toString(), + post.getCommentCount(), + post.getIsExternal(), + post.getMember().getId() + ); + + Long postAuthorId = post.getMember().getId(); + + List simpleCommentDTOs = comments.stream() + .map(comment -> new PostDetailIMGResponse.SimpleCommentDTO( + comment.getId(), + comment.getContent(), + comment.getCreatedAt().toString(), + comment.getMember().getId(), + comment.getMember().getId().equals(postAuthorId) + )) + .collect(Collectors.toList()); + + PostDetailIMGResponse response = new PostDetailIMGResponse(simplePostDTO, imageUrls, simpleCommentDTOs); + return Response.ok(response); } - // 글 작성 - @PostMapping("/donggeul/create") - public Response createDonggeulPost(@RequestBody CreatePostRequest createPostRequest) { - Post newPost = postService.createDonggeulPost(createPostRequest); - return Response.ok(newPost); + // 글 작성 (Donggeul) - 이미지 포함 + @PostMapping(value = "/donggeul/create", consumes = "multipart/form-data") + public Response createDonggeulPost( + @RequestParam("isExternal") Boolean isExternal, + @RequestParam("title") String title, + @RequestParam("content") String content, + @RequestParam("hashtag") String hashtag, + @RequestParam(value = "images", required = false) List images + ) { + // SecurityContextUtil을 사용하여 현재 로그인된 사용자의 memberId를 가져오기 + Long memberId = securityContextUtil.getContextMemberInfo().getMemberId(); + + DonggeulPostRequest donggeulPostRequest = new DonggeulPostRequest(); + donggeulPostRequest.setIsExternal(isExternal); + donggeulPostRequest.setTitle(title); + donggeulPostRequest.setContent(content); + donggeulPostRequest.setHashtag(hashtag); + donggeulPostRequest.setImages(images); + donggeulPostRequest.setMemberId(memberId); + + Post newPost = postService.createDonggeulPost(donggeulPostRequest); + + PostDetailIMGResponse.SimplePostDTO simplePostDTO = new PostDetailIMGResponse.SimplePostDTO( + newPost.getId(), + newPost.getTitle(), + newPost.getContent(), + newPost.getHashtag(), + newPost.getPostType(), + newPost.getCreatedAt().toString(), + newPost.getCommentCount(), + newPost.getIsExternal(), + newPost.getMember().getId() + ); + + List imageUrls = postService.getImageUrlsByPostId(newPost.getId()); + List comments = new ArrayList<>(); + + PostDetailIMGResponse response = new PostDetailIMGResponse(simplePostDTO, imageUrls, comments); + return Response.ok(response); } // 댓글 작성 @PostMapping("/donggeul/{postId}/comment") - public Response createComment(@PathVariable Long postId, @RequestBody String content) { - Comment newComment = commentService.createComment(postId, content); - return Response.ok(newComment); - } + public CommentResponse createComment(@PathVariable Long postId, @RequestBody CreateCommentRequest request) { + // SecurityContextUtil을 사용하여 현재 로그인된 사용자의 memberId를 가져오기 + Long memberId = securityContextUtil.getContextMemberInfo().getMemberId(); + + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("Invalid post ID")); + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("Invalid member ID")); + Comment comment = Comment.builder() + .content(request.getContent()) + .post(post) + .member(member) + .createdAt(LocalDateTime.now()) + .build(); + + postService.incrementCommentCount(postId); + commentRepository.save(comment); + + boolean isAuthor = member.getId().equals(post.getMember().getId()); + + return new CommentResponse( + comment.getId(), + postId, + member.getId(), + comment.getContent(), + comment.getCreatedAt().toString(), + isAuthor + ); + } } diff --git a/src/main/java/com/nemo/dong_geul_be/board/controller/BoardJJController.java b/src/main/java/com/nemo/dong_geul_be/board/controller/BoardJJController.java index 2efc98a..d3e9245 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/controller/BoardJJController.java +++ b/src/main/java/com/nemo/dong_geul_be/board/controller/BoardJJController.java @@ -1,11 +1,15 @@ package com.nemo.dong_geul_be.board.controller; -import com.nemo.dong_geul_be.board.domain.dto.request.CreatePostRequest; +import com.nemo.dong_geul_be.board.domain.dto.request.CreateCommentRequest; +import com.nemo.dong_geul_be.board.domain.dto.request.JejalPostRequest; +import com.nemo.dong_geul_be.board.domain.dto.response.CommentResponse; import com.nemo.dong_geul_be.board.domain.dto.response.PostDTO; import com.nemo.dong_geul_be.board.domain.dto.response.PostDetailResponse; import com.nemo.dong_geul_be.board.domain.entity.Comment; import com.nemo.dong_geul_be.board.domain.entity.Post; +import com.nemo.dong_geul_be.board.repository.CommentRepository; +import com.nemo.dong_geul_be.board.repository.PostRepository; import com.nemo.dong_geul_be.board.service.CommentService; import com.nemo.dong_geul_be.board.service.PostService; import com.nemo.dong_geul_be.global.response.Response; @@ -13,6 +17,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.stream.Collectors; @RestController @RequestMapping("/api") @@ -21,6 +26,8 @@ public class BoardJJController { //재잘재잘 : 자유게시판 private final PostService postService; private final CommentService commentService; + private final PostRepository postRepository; + private final CommentRepository commentRepository; //메인페이지 @GetMapping("/jejal") // 재잘재잘은 booelean이 1인 게시글 @@ -47,25 +54,84 @@ public Response> getPostTrueExternalTruePosts() { // 게시글 상세보기 @GetMapping("/jejal/{postId}") public Response getPostDetails(@PathVariable Long postId) { + Post post = postService.getPostById(postId); - List comments = commentService.getCommentsByPost(postId); - PostDetailResponse response = new PostDetailResponse(post, comments); + PostDetailResponse.SimplePostDTO postDto = new PostDetailResponse.SimplePostDTO( + post.getId(), + post.getTitle(), + post.getContent(), + post.getHashtag(), + post.getPostType(), + post.getCreatedAt().toString(), + post.getCommentCount(), + post.getIsExternal(), + post.getMember().getId() // memberId 추가 + ); + + List commentDtos = commentService.getCommentsByPost(postId).stream() + .map(comment -> new PostDetailResponse.SimpleCommentDTO( + comment.getId(), + comment.getContent(), + comment.getCreatedAt().toString(), + comment.getMember().getId(), + comment.getMember().getId().equals(post.getMember().getId()) // 게시글 작성자와 댓글 작성자 비교 + )) + .collect(Collectors.toList()); + + PostDetailResponse response = new PostDetailResponse(postDto, commentDtos); + return Response.ok(response); } - // 글 작성 + + // 글 작성 (Jejal) - 이미지 없이 @PostMapping("/jejal/create") - public Response createJejalPost(@RequestBody CreatePostRequest createPostRequest) { - Post newPost = postService.createJejalPost(createPostRequest); - return Response.ok(newPost); + public Response createJejalPost(@RequestBody JejalPostRequest jejalPostRequest) { + + Post newPost = postService.createJejalPost(jejalPostRequest); + + PostDetailResponse.SimplePostDTO simplePostDTO = new PostDetailResponse.SimplePostDTO( + newPost.getId(), + newPost.getTitle(), + newPost.getContent(), + newPost.getHashtag(), + newPost.getPostType(), + newPost.getCreatedAt().toString(), + newPost.getCommentCount(), + newPost.getIsExternal(), + newPost.getMember().getId() // memberId 추가 + ); + + // 응답 반환 + return Response.ok(simplePostDTO); } // 댓글 작성 @PostMapping("/jejal/{postId}/comment") - public Response createComment(@PathVariable Long postId, @RequestBody String content) { - Comment newComment = commentService.createComment(postId, content); - return Response.ok(newComment); - } + public Response createComment( + @PathVariable Long postId, + @RequestBody CreateCommentRequest request) { + + // 요청 객체에 postId 설정 + request.setPostId(postId); + + // commentService에 postId와 request를 전달 + Comment newComment = commentService.createComment(postId, request); + // 게시글 작성자 ID와 댓글 작성자 ID 비교하여 isAuthor 값 설정 + boolean isAuthor = newComment.getMember().getId().equals(newComment.getPost().getMember().getId()); + + // CommentResponseDto로 변환 후 반환 + CommentResponse responseDto = new CommentResponse( + newComment.getId(), + postId, + newComment.getMember().getId(), + newComment.getContent(), + newComment.getCreatedAt().toString(), + isAuthor + ); + + return Response.ok(responseDto); + } } diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreatePostRequest.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreateCommentRequest.java similarity index 55% rename from src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreatePostRequest.java rename to src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreateCommentRequest.java index 8b4defd..1824ad4 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreatePostRequest.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/CreateCommentRequest.java @@ -5,9 +5,8 @@ @Getter @Setter -public class CreatePostRequest { - private Boolean isExternal; - private String title; +public class CreateCommentRequest { + private Long postId; + private Long memberId; private String content; - private String hashtag; } \ No newline at end of file diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/DonggeulPostRequest.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/DonggeulPostRequest.java new file mode 100644 index 0000000..891d6ad --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/DonggeulPostRequest.java @@ -0,0 +1,19 @@ +package com.nemo.dong_geul_be.board.domain.dto.request; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Getter +@Setter +//동글동글(이미지 o)을 위한 Request +public class DonggeulPostRequest { + private Boolean isExternal; + private String title; + private String content; + private String hashtag; + private List images; + private Long memberId; +} \ No newline at end of file diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/JejalPostRequest.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/JejalPostRequest.java new file mode 100644 index 0000000..c4d0b0a --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/request/JejalPostRequest.java @@ -0,0 +1,14 @@ +package com.nemo.dong_geul_be.board.domain.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class JejalPostRequest { + private Long memberId; // memberId + private Boolean isExternal; // 외부 여부 + private String title; // 제목 + private String content; // 내용 + private String hashtag; // 해시태그 +} diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/CommentResponse.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/CommentResponse.java new file mode 100644 index 0000000..696842a --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/CommentResponse.java @@ -0,0 +1,22 @@ +package com.nemo.dong_geul_be.board.domain.dto.response; + +import lombok.Data; + +@Data +public class CommentResponse { + private Long id; + private Long postId; + private Long memberId; + private String content; + private String createdAt; + private boolean isAuthor; // 게시글 작성자인지 여부 추가 + + public CommentResponse(Long id, Long postId, Long memberId, String content, String createdAt, boolean isAuthor) { + this.id = id; + this.postId = postId; + this.memberId = memberId; + this.content = content; + this.createdAt = createdAt; + this.isAuthor = isAuthor; + } +} diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDTO.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDTO.java index df5344e..ab8e4c3 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDTO.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDTO.java @@ -6,13 +6,24 @@ import java.time.LocalDateTime; + +//게시글 목록이나 검색에서 가져올 DTO @Data -@Getter -@AllArgsConstructor public class PostDTO { private String title; private String content; private LocalDateTime createdAt; private int commentCount; private Boolean isExternal; + private String imageUrl; // 이미지 URL 필드 추가 + + //Response 간소화 + public PostDTO(String title, String content, LocalDateTime createdAt, int commentCount, Boolean isExternal, String imageUrl) { + this.title = title; //제목 + this.content = content; //내용 + this.createdAt = createdAt; //작성시간 + this.commentCount = commentCount; //댓글 수 + this.isExternal = isExternal; //교내외 + this.imageUrl = imageUrl; //대표이미지 Url + } } diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailIMGResponse.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailIMGResponse.java new file mode 100644 index 0000000..697e997 --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailIMGResponse.java @@ -0,0 +1,60 @@ +package com.nemo.dong_geul_be.board.domain.dto.response; + +import lombok.Getter; +import java.util.List; + +// 간단화된 Response (동글) +@Getter +public class PostDetailIMGResponse { + private SimplePostDTO post; // 간단한 게시글 정보 + private List imageUrls; // 이미지 URL 목록 + private List comments; // 간단한 댓글 목록 + + public PostDetailIMGResponse(SimplePostDTO post, List imageUrls, List comments) { + this.post = post; + this.imageUrls = imageUrls; + this.comments = comments; + } + + @Getter + public static class SimplePostDTO { + private Long id; + private String title; + private String content; + private String hashtag; + private Boolean postType; + private String createdAt; + private Integer commentCount; + private Boolean isExternal; + private Long memberId; + + public SimplePostDTO(Long id, String title, String content, String hashtag, Boolean postType, String createdAt, Integer commentCount, Boolean isExternal, Long memberId) { + this.id = id; + this.title = title; + this.content = content; + this.hashtag = hashtag; + this.postType = postType; + this.createdAt = createdAt; + this.commentCount = commentCount; + this.isExternal = isExternal; + this.memberId = memberId; + } + } + + @Getter + public static class SimpleCommentDTO { + private Long id; + private String content; + private String createdAt; + private Long memberId; + private boolean isAuthor; // 댓글 작성자가 게시글 작성자인지 여부 + + public SimpleCommentDTO(Long id, String content, String createdAt, Long memberId, boolean isAuthor) { + this.id = id; + this.content = content; + this.createdAt = createdAt; + this.memberId = memberId; + this.isAuthor = isAuthor; + } + } +} diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailResponse.java b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailResponse.java index b0a9892..e0489dd 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailResponse.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/dto/response/PostDetailResponse.java @@ -1,18 +1,61 @@ package com.nemo.dong_geul_be.board.domain.dto.response; -import com.nemo.dong_geul_be.board.domain.entity.Comment; -import com.nemo.dong_geul_be.board.domain.entity.Post; import lombok.Getter; import java.util.List; +// 이미지를 포함하지 않는 Response (Jejal) @Getter public class PostDetailResponse { - private Post post; - private List comments; + private SimplePostDTO post; // 게시글 정보 + private List comments; // 댓글 목록 - public PostDetailResponse(Post post, List comments) { + public PostDetailResponse(SimplePostDTO post, List comments) { this.post = post; this.comments = comments; } -} \ No newline at end of file + + @Getter + public static class SimplePostDTO { + private Long id; + private String title; + private String content; + private String hashtag; + private Boolean postType; + private String createdAt; + private int commentCount; + private Boolean isExternal; + private Long memberId; // memberId 추가 + + public SimplePostDTO(Long id, String title, String content, String hashtag, Boolean postType, + String createdAt, int commentCount, Boolean isExternal, Long memberId) { + this.id = id; + this.title = title; + this.content = content; + this.hashtag = hashtag; + this.postType = postType; + this.createdAt = createdAt; + this.commentCount = commentCount; + this.isExternal = isExternal; + this.memberId = memberId; // memberId 설정 + } + } + + @Getter + public static class SimpleCommentDTO { + private Long id; + private Long memberId; + private String content; + private String createdAt; + private boolean isAuthor; + + + public SimpleCommentDTO(Long id, String content, String createdAt, Long memberId, boolean isAuthor) { + this.id = id; + this.content = content; + this.createdAt = createdAt; + this.memberId = memberId; + this.isAuthor = isAuthor; + } + } +} diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Comment.java b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Comment.java index 3c2d753..4ccc27a 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Comment.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Comment.java @@ -22,7 +22,7 @@ public class Comment { private Post post; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = true) //임시로 null값 가능하도록 + @JoinColumn(name = "member_id", nullable = false) private Member member; @Column(nullable = false, columnDefinition = "TEXT") diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post.java b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post.java index 747085a..fd7cf89 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post.java @@ -34,7 +34,7 @@ public class Post { private LocalDateTime createdAt; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id", nullable = true) //임시로 테스트할때 null값 가능하도록 + @JoinColumn(name = "member_id", nullable = false) private Member member; @Setter diff --git a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post_IMG.java b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post_IMG.java index 345b013..ad22c73 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post_IMG.java +++ b/src/main/java/com/nemo/dong_geul_be/board/domain/entity/Post_IMG.java @@ -19,9 +19,7 @@ public class Post_IMG { @JoinColumn(name = "post_id", nullable = false) private Post post; - @Column(nullable = false, length = 50) + //url 최대길이 변경 + @Column(nullable = false, length = 255) private String url; // s3 주소 - - @Column(nullable = false) - private Boolean state; // 이미지 존재 여부 } diff --git a/src/main/java/com/nemo/dong_geul_be/board/repository/PostImageRepository.java b/src/main/java/com/nemo/dong_geul_be/board/repository/PostImageRepository.java new file mode 100644 index 0000000..b15a65a --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/repository/PostImageRepository.java @@ -0,0 +1,14 @@ +package com.nemo.dong_geul_be.board.repository; + +import com.nemo.dong_geul_be.board.domain.entity.Post_IMG; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface PostImageRepository extends JpaRepository { + + // 특정 게시글에 속한 이미지 목록을 가져오는 메서드 + List findByPostId(Long postId); +} \ No newline at end of file diff --git a/src/main/java/com/nemo/dong_geul_be/board/service/CommentService.java b/src/main/java/com/nemo/dong_geul_be/board/service/CommentService.java index 7f6676c..0edb9b4 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/service/CommentService.java +++ b/src/main/java/com/nemo/dong_geul_be/board/service/CommentService.java @@ -1,10 +1,12 @@ package com.nemo.dong_geul_be.board.service; +import com.nemo.dong_geul_be.board.domain.dto.request.CreateCommentRequest; import com.nemo.dong_geul_be.board.domain.entity.Comment; import com.nemo.dong_geul_be.board.domain.entity.Post; import com.nemo.dong_geul_be.board.repository.CommentRepository; import com.nemo.dong_geul_be.board.repository.PostRepository; import com.nemo.dong_geul_be.member.domain.entity.Member; +import com.nemo.dong_geul_be.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,18 +19,20 @@ public class CommentService { private final CommentRepository commentRepository; private final PostRepository postRepository; + private final MemberRepository memberRepository; + private final PostService postService; - //로그인이 아직 없기에 member 지움 - public Comment createComment(Long postId, String content - //, Member member - ) { - Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("Invalid post ID")); + public Comment createComment(Long postId, CreateCommentRequest request) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("Invalid post ID")); + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new IllegalArgumentException("Invalid member ID")); Comment comment = Comment.builder() - .content(content) + .content(request.getContent()) .post(post) - //.member(member) + .member(member) .createdAt(LocalDateTime.now()) .build(); diff --git a/src/main/java/com/nemo/dong_geul_be/board/service/PostService.java b/src/main/java/com/nemo/dong_geul_be/board/service/PostService.java index 56673fc..36f6269 100644 --- a/src/main/java/com/nemo/dong_geul_be/board/service/PostService.java +++ b/src/main/java/com/nemo/dong_geul_be/board/service/PostService.java @@ -1,11 +1,18 @@ package com.nemo.dong_geul_be.board.service; -import com.nemo.dong_geul_be.board.domain.dto.request.CreatePostRequest; +import com.nemo.dong_geul_be.board.domain.dto.request.DonggeulPostRequest; +import com.nemo.dong_geul_be.board.domain.dto.request.JejalPostRequest; import com.nemo.dong_geul_be.board.domain.dto.response.PostDTO; import com.nemo.dong_geul_be.board.domain.entity.Post; +import com.nemo.dong_geul_be.board.domain.entity.Post_IMG; +import com.nemo.dong_geul_be.board.repository.PostImageRepository; import com.nemo.dong_geul_be.board.repository.PostRepository; +import com.nemo.dong_geul_be.member.domain.entity.Member; +import com.nemo.dong_geul_be.member.domain.entity.Role; +import com.nemo.dong_geul_be.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.List; @@ -17,33 +24,48 @@ public class PostService { private final PostRepository postRepository; + private final S3Service s3Service; + private final PostImageRepository postImageRepository; + private final MemberRepository memberRepository; // 재잘재잘 게시글 가져올 때 - public List getTruePostTypePosts() { //재잘재잘 + public List getTruePostTypePosts() { List posts = postRepository.findByPostTypeTrue(); return posts.stream() - .map(post -> new PostDTO( //게시글 목록으로 제목, 내용, 날짜, 댓글 수, 교외/교내 분류 - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + // 해당 게시글에 연결된 이미지 중 첫 번째 이미지를 가져옴 + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl // 이미지가 있으면 첫 번째 이미지 URL, 없으면 null + ); + }) .collect(Collectors.toList()); - } // 재잘재잘 게시글이면서 교내 동아리 public List getPostTrueExternalFalsePosts() { List posts = postRepository.findByPostTypeTrueAndIsExternalFalse(); return posts.stream() - .map(post -> new PostDTO( - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl + ); + }) .collect(Collectors.toList()); } @@ -51,29 +73,37 @@ public List getPostTrueExternalFalsePosts() { public List getPostTrueExternalTruePosts() { List posts = postRepository.findByPostTypeTrueAndIsExternalTrue(); return posts.stream() - .map(post -> new PostDTO( - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl + ); + }) .collect(Collectors.toList()); } - // 재잘재잘 게시판에서 게시글 생성 (post_type = true) - public Post createJejalPost(CreatePostRequest request - //, Member member - ) { + public Post createJejalPost(JejalPostRequest jejalPostRequest) { + // Member 조회 + Member member = memberRepository.findById(jejalPostRequest.getMemberId()) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + // Post 생성 Post post = Post.builder() - .isExternal(request.getIsExternal()) - .title(request.getTitle()) - .content(request.getContent()) - .hashtag(request.getHashtag()) - .postType(true) // 재잘재잘 게시판은 무조건 true + .title(jejalPostRequest.getTitle()) + .content(jejalPostRequest.getContent()) + .hashtag(jejalPostRequest.getHashtag()) + .postType(true) // 재잘은 무조건 true .createdAt(LocalDateTime.now()) - //.member(member) + .member(member) // 조회한 Member 설정 .commentCount(0) + .isExternal(jejalPostRequest.getIsExternal()) .build(); return postRepository.save(post); @@ -85,13 +115,20 @@ public Post createJejalPost(CreatePostRequest request public List getFalsePostTypePosts() { List posts = postRepository.findByPostTypeFalse(); return posts.stream() - .map(post -> new PostDTO( //게시글 목록으로 제목, 내용, 날짜, 댓글 수, 교외/교내 분류 - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + // 해당 게시글에 연결된 이미지 중 첫 번째 이미지를 가져옴 + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl // 이미지가 있으면 첫 번째 이미지 URL, 없으면 null + ); + }) .collect(Collectors.toList()); } @@ -99,13 +136,19 @@ public List getFalsePostTypePosts() { public List getPostFalseExternalFalsePosts() { List posts = postRepository.findByPostTypeFalseAndIsExternalFalse(); return posts.stream() - .map(post -> new PostDTO( - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl + ); + }) .collect(Collectors.toList()); } @@ -113,13 +156,19 @@ public List getPostFalseExternalFalsePosts() { public List getPostFalseExternalTruePosts() { List posts = postRepository.findByPostTypeFalseAndIsExternalTrue(); return posts.stream() - .map(post -> new PostDTO( - post.getTitle(), - post.getContent(), - post.getCreatedAt(), - post.getCommentCount(), - post.getIsExternal() - )) + .map(post -> { + List images = postImageRepository.findByPostId(post.getId()); + String imageUrl = images.isEmpty() ? null : images.get(0).getUrl(); + + return new PostDTO( + post.getTitle(), + post.getContent(), + post.getCreatedAt(), + post.getCommentCount(), + post.getIsExternal(), + imageUrl + ); + }) .collect(Collectors.toList()); } @@ -128,11 +177,20 @@ public Post getPostById(Long postId) { .orElseThrow(() -> new NoSuchElementException("게시글을 찾을 수 없습니다.")); } - // 로그인이 없어 우선 테스트로 member 지움 - // 동글동글 게시판에서 게시글 생성 (post_type = false) - public Post createDonggeulPost(CreatePostRequest request - //, Member member - ) { + + +// 동글동글 게시판에서 게시글 생성 (post_type = false) + public Post createDonggeulPost(DonggeulPostRequest request) { + // memberId로 Member 조회 + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new IllegalArgumentException("Member not found")); + + // 역할 확인: MANAGER가 아니면 예외 발생 + if (!member.getRole().equals(Role.MANAGER)) { + throw new IllegalArgumentException("Only MANAGER role can create a post"); + //response, code 방식으로 해결하고 싶음.. + } + Post post = Post.builder() .isExternal(request.getIsExternal()) .title(request.getTitle()) @@ -140,17 +198,42 @@ public Post createDonggeulPost(CreatePostRequest request .hashtag(request.getHashtag()) .postType(false) // 동글동글 게시판은 무조건 false .createdAt(LocalDateTime.now()) - //.member(member) + .member(member) .commentCount(0) .build(); - return postRepository.save(post); + post = postRepository.save(post); + + // 이미지 파일 업로드 처리 + if (request.getImages() != null && !request.getImages().isEmpty()) { + for (MultipartFile image : request.getImages()) { + String imageUrl = s3Service.uploadFile(image); + Post_IMG postImg = Post_IMG.builder() + .post(post) + .url(imageUrl) + .build(); + postImageRepository.save(postImg); + } + } + + return post; } + // 댓글 작성 시 commentCount++ public void incrementCommentCount(Long postId) { Post post = postRepository.findById(postId).orElseThrow(() -> new RuntimeException("Post not found")); post.setCommentCount(post.getCommentCount() + 1); postRepository.save(post); } + + public List getImageUrlsByPostId(Long postId) { + // 해당 게시글의 이미지 목록 가져오기 + List postImages = postImageRepository.findByPostId(postId); + + // 이미지 URL만 추출하여 리스트로 반환 + return postImages.stream() + .map(Post_IMG::getUrl) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/nemo/dong_geul_be/board/service/S3Service.java b/src/main/java/com/nemo/dong_geul_be/board/service/S3Service.java new file mode 100644 index 0000000..7d56d42 --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/board/service/S3Service.java @@ -0,0 +1,43 @@ +package com.nemo.dong_geul_be.board.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class S3Service { + + private final S3Client s3Client; + private final String BUCKET_NAME = "donggeul"; + + public String uploadFile(MultipartFile file) { + String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename().replaceAll("[^a-zA-Z0-9\\.\\-]", "_"); + + try (InputStream inputStream = file.getInputStream()) { + s3Client.putObject( + PutObjectRequest.builder() + .bucket(BUCKET_NAME) + .key(fileName) + .build(), + software.amazon.awssdk.core.sync.RequestBody.fromInputStream(inputStream, file.getSize()) + ); + // 파일 업로드 후 URL 반환 + return getFileUrl(fileName); + } catch (S3Exception | IOException e) { + System.err.println("파일 업로드 오류: " + e.getMessage()); + throw new RuntimeException("S3에 파일을 업로드하는 중 오류가 발생했습니다.", e); + } + } + + private String getFileUrl(String fileName) { + return s3Client.utilities().getUrl(builder -> builder.bucket(BUCKET_NAME).key(fileName)).toExternalForm(); + } +} diff --git a/src/main/java/com/nemo/dong_geul_be/global/config/AwsS3Config.java b/src/main/java/com/nemo/dong_geul_be/global/config/AwsS3Config.java new file mode 100644 index 0000000..90a7adf --- /dev/null +++ b/src/main/java/com/nemo/dong_geul_be/global/config/AwsS3Config.java @@ -0,0 +1,24 @@ +package com.nemo.dong_geul_be.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Configuration; + +@Configuration +public class AwsS3Config { + + @Bean + public S3Client s3Client() { + S3Configuration s3Config = S3Configuration.builder() + .pathStyleAccessEnabled(false) // Virtual-hosted style + .build(); + + return S3Client.builder() + .region(Region.AP_NORTHEAST_2) // 서울 리전 설정 + .serviceConfiguration(s3Config) + .build(); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 533119a..375fef0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -47,5 +47,18 @@ jwt: access: ${JWT_ACCESS_EXPIRE} refresh: ${JWT_REFRESH_EXPIRE} +## AWS S3 +cloud: + aws: + credentials: + accessKey: ${AWS_ACCESS_KEY_ID} + secretKey: ${AWS_SECRET_ACCESS_KEY} + region: + static: ap-northeast-2 + s3: + bucket: donggeul - +aws: + s3: + connectionTimeout: 5000 + socketTimeout: 5000